SourceForge.net Logo

eval Documentation

1

Version:
0.1

Overview

eval is a header-only library for writing C++ scripts. The main aim of the library is to provide means for calling compiled C++ functions/methods/constructors from within a script (which does not have to be compiled). An eval script can take arguments from the compiled code, as well as return arbitrary values. In order to make a function/method/constructor work with the library, the user only has to mention it in an eval header file, which is not unlike a normal C++ header file. (Although, in an eval header, often times just the name of the function/method is necessary, rather than the full prototype.)

Proof of Concept Only

Please note that this is a proof of concept release. Hence, the release is far from feature complete and likely contains serious bugs. Furthermore, the public API is not yet guaranteed stable. In particular, the actual syntax of the eval scripts may well change in later releases.

Contents

Download

A zip release, including all dependencies, is available from SourceForge ( eval home ). This zip files includes the required Boost headers, as well as Boost.Build and Boost.Jam, which are only necessary to run the unit tests. However, development will take place in svn and the online documentation may describe features only available in the most recent svn version. Hence, if you download the code from svn, you may wish to obtain a Boost distribution separately, if you don't already have one. Alternatively, copy the following files/folders from the zip file into your svn checkout:

Example checkout using command line client:

svn co https://eval.svn.sourceforge.net/svnroot/eval/trunk trunk 
    

Quick Start

Writing the necessary files

In reality, to use most of the functionality of the library, only a very small subset of the API is necessary. Hence, a simple example follows, which should give the reader a good idea about what the library can do. First of all, we need to have one or more classes that we might wish to use from within the script. For this example, we will use a simple Point class. The code follows here, but it also available from the example link at the top of the page, or the example subdirectory of the library.
// Point.hpp
// An extremely simple point class.
class Point
{
public:
    Point()
      : _x(0), _y(0)
    {
    }

    Point(int x, int y)
      : _x(x), _y(y)
    {
    }

    int get_x() const
    {
        return _x;
    }

    int get_y() const
    {
        return _y;
    }

    void set_x(int x)
    {
        _x = x;
    }

    void set_y(int y)
    {
        _y = y;
    }

    void add_to_this(const Point& other)
    {
        _x += other._x;
        _y += other._y;
    }

    // A somewhat artificial overload; note the corresponding
    // eval header syntax.
    void add_to_this(int x, int y)
    {
        _x += x;
        _y += y;
    }

private:
    int _x;
    int _y;

    friend Point add_points(Point& a, Point& b);
};




inline Point add_points(Point& a, Point& b)
{
    return Point(a._x + b._x, a._y + b._y);
}



// A somewhat artificial inheritance structure; note the corresponding
// eval header syntax.
class ThreeDimPoint : public Point
{
public:
    ThreeDimPoint(int x, int y, int z)
      : Point(x, y), _z(z)
    {
    }

    ThreeDimPoint()
      : Point(), _z(0)
    {
    }

    int get_z() const
    {
        return _z;
    }

    void set_z(int z)
    {
        _z = z;
    }

private:
    int _z;
};


Next, we show the corresponding eval header (which also includes some other standard eval headers for additional functionality):

// eval_example_header.ehpp

// Defines the basic types (e.g. int, double, ...)
#include "eval_headers/basic_types.ehpp"

// These headers are nowhere near complete.
#include "eval_headers/string.ehpp"
#include "eval_headers/ostream.ehpp"


EVAL_REGISTER_TYPE(Point)
    EVAL_REGISTER_CONSTRUCTOR(())             // default constructor
    EVAL_REGISTER_CONSTRUCTOR((int, int))
    EVAL_REGISTER_CONSTRUCTOR((const Point&)) // copy constructor
    EVAL_REGISTER_METHOD(get_x)
    EVAL_REGISTER_METHOD(get_y)
    EVAL_REGISTER_METHOD(set_x)
    EVAL_REGISTER_METHOD(set_y)
    EVAL_REGISTER_METHOD_OVERLOADED(void, (const Point&), add_to_this)
    EVAL_REGISTER_METHOD_OVERLOADED(void, (int, int), add_to_this)
EVAL_END_TYPE


EVAL_REGISTER_FUNCTION(add_points)


EVAL_REGISTER_TYPE(ThreeDimPoint)
    // Note: EVAL_INHERIT_FROM semantically really means "implements".
    // Inheritance hierarchy is irrelevant; however, this class must
    // implement/inherit all the methods (excluding constructors) in Point, or
    // compilation will fail (in C++ it is possible to create a subclass which
    // does not implemented all the methods of its superclass exactly).
    // If this class overwrites methods from the base class, you can list
    // them explicitly, but that's not necessary.
    EVAL_INHERIT_FROM(Point)

    EVAL_REGISTER_CONSTRUCTOR(())
    EVAL_REGISTER_CONSTRUCTOR((int, int, int))
    EVAL_REGISTER_CONSTRUCTOR((const ThreeDimPoint&))
    EVAL_REGISTER_METHOD(get_z)
    EVAL_REGISTER_METHOD(set_z)
EVAL_END_TYPE


// Note that it is possible to register functions/methods/types
// with a different name than in their C++ source (so that the
// scripts would be able to refer to an alias name).

We can now use these files in our example program:

// eval_example.cpp

#include <string>
#include <iostream>
#include <assert.h>

// The eval header defines 2 functions, eval_file and eval (takes const char *)
#include "eval.hpp"

// The val class is a generic class for wrapping arbitary instances/primitives.
#include "val.hpp"

// A plain old c++ class, serving as an example.
#include "Point.hpp"

// The EVAL_INCLUDE_FILE1 is the eval header that determines which
// functions/methods/constructors the scripts have access to.
#define EVAL_INCLUDE_FILE1 "eval_example_header.ehpp"

// Some preprocessor magic happens at this point. Suffices to say that
// you now have a class deriving from eval::execution_context, called
// MyExecContext. This custom excecution context knows about
// functions/methods/constructors as per EVAL_INCLUDE_FILE1.
// (You can create more than 1 execution context.)
#define EVAL_EXECUTION_CONTEXT_NAME MyExecContext
#include "create_execution_context_definition.hpp"



int main()
{
    MyExecContext e;   // Create our custom execution context.
    e.register_all();  // Register all functions/methods/constructors. In some
                       // circumstances, the execution context can be used
                       // without this call.
    
    // Make std::cout available to the script (in future library versions,
    // this may be done in the appropriate eval standard header).
    e["std::cout"] = eval::val(&std::cout, eval::UNMANAGED_PTR);

    // make some Points available to the script
    e["point1"] = eval::val(new Point(4, 5), eval::TRANSFER_OWNERSHIP);
    Point point2(6, 7);
    e["point2"] = eval::val(&point2, eval::UNMANAGED_PTR);

    // execute our example script (the script is in the same directory;
    // specifying a more complete path just helps the automated testing system)
    eval_file(e, "../examples/eval_example_script.epp");
    
    // We have access to variables defined in the script.
    Point *point_rv = e.extended_val_cast_ptr<Point>(e["point_rv"]);
    assert(point_rv->get_x() == 12);
    assert(point_rv->get_y() == 14);

    // The script also modified existing objects (arguments)
    assert(point2.get_x() == 11);
    assert(point2.get_y() == 16);

    // Note that various casting methods exist, allowing you to attempt
    // to convert return values to a particular type (or just get a string
    // or int representation, if possible).

    return 0;
}

Finally, here's the script referred to by the main program; this can be written after compiling the above:

// eval_example_script.epp

// std::cout, point1 and point2 come in as arguments

// call operator<< function
std::cout << "Hello world from an eval script" << "\n";

val point3; // Declare new variable (accessible from calling program).
            // All variables have type val; declarations go on their own line.
point3 = Point(point1.get_x(), point2.get_y());

// modify an argument passed in
point2.add_to_this(point3);
point2.add_to_this(1, 2);

val point_rv;
point_rv = add_points(point1, ThreeDimPoint(8, 9, 6)); // adding in 2D only!


// Note:
val temp;
temp = 3;             // const int
temp = int(3);        // int
temp = Point(point3); // make a copy

Compiling the Example

Compiling should be relatively easy, since eval is a header-only library. Just open a command window and change to the examples directory; then invoke your compiler on eval_example.cpp, specifying the current directory and the parent directory as part of the include path. (If you downloaded the code from svn, you may also have to add the path to your Boost headers as an include directory, unless the headers are in the appropriate system directory.) For example, for g++ users:
    g++ -Wall -I. -I.. eval_example.cpp
    ./a.out
    

Warning:
The example was tested on g++ 4.3 (Linux), g++ 4.0 (Mac OS X (since svn revision 6)) and msvc 8. g++ -Wall compiles the code without warnings. However, msvc 8 may generate extremely large amounts of warnings (i.e. thousands of lines) all originating from a very similar cause. Fixing these warnings (by adding explicit static_casts) would change the semantics of the program. Hence, disabling the warnings may be necessary; refer to TODO.

Running the Unit-Tests

The unit tests can be run by opening a command window and changing into the tests directory. Here, execute one of the following programs (which will compile the tests):

On msvc 8, a very small subset of the tests may not compile, see Known Bugs and Limitations.

Note:
Neither bjam nor Boost.Build are part of this library. For further details, or to obtain a bjam executable for another operating system, please see their respective manuals. There you will also find documentation on how to set the compiler used, if the automatic choice is inappropriate. Furthermore, the tests will only run if you have the boost headers installed, either in a system directory, or else in the main directory of the library.
Lastly, if you have the valgrind tool, then using it during unit-testing is a good idea. To enable this, set memcheck=1 when running bjam. E.g.:
    ./bjam_linux_x68 memcheck=1
    
However, please note that the unit-testing library actually causes some errors, which valgrind picks up.

Compiling the Documentation

You must have Doxygen installed. Open a command prompt and change to the docs directory. Then:
    doxygen doxygen.config
    

Design Overview

eval::val

The section aims to give a high level overview of the library. Firstly, the eval::val class is somewhat like Boost.Any. However, it is designed to wrap a specific instance (i.e. it usually uses shallow copy). A brief example follows:
// val_example.cpp

#include "assert.h"
#include <iostream>

#include "val.hpp"

int main()
{
    eval::val v1(new std::string("hello world"), eval::TRANSFER_OWNERSHIP);
    eval::val v2(v1); // make a shallow copy - reference counted

    int a = 8;
    eval::val v3(a, eval::COPY_VALUE);
    eval::val v4 = v3; // make a real copy
    int *the_value = eval::val_cast_ptr<int>(v3);
    assert(*the_value == 8);
    assert(v3.coerce_string() == std::string("8")); // works with ostreamable types
    assert(v3.coerce_int() == 8); // works with numeric types (e.g. int, double, ...)

   
    try
    {
        eval::val v5(8, eval::COPY_VALUE);
        int *x;
        x = eval::val_cast_ptr<int>(v5); // const int, so can only cast to const int
        assert(0);
    }
    catch(eval::bad_val_cast& e)
    {
        // val_cast_ptr can only succeed if the types match _exactly_.
        // eval::execution_context provides conversion casts.
    }
    
    // For globals or local variables declared on the stack:
    eval::val v6(&std::cout, eval::UNMANAGED_PTR);


    // Other variants exist, taking arrays or shared pointer arguments.

    return 0;
}

For implementation details, see also eval::detail::storage_base_common and eval::detail::storage_base_nonconst.

eval::fwrap

eval::fwrap is essentially the same as eval::val, but for functions/methods/constructors. Any function that is wrapped will be callable with the same generic prototype. That is, it will take a single argument, a container (i.e. vector) of eval::val, and return an eval::val. See eval::fwrap::operator()().

eval::execution_context

The eval::execution_context essentially stores methods/constructors/functions (as fwraps), types and global variables (as vals). It has facilities for calling functions/methods, given the name as a string, as well as for doing type conversions and overload resolution. Normally, a subclass of execution_context, EVAL_EXECUTION_CONTEXT_NAME is auto-generated from an eval header at compile time (using the preprocessor).

eval

Lastly, the eval() function simply provides a convenient interface for the execution context, using C++ like syntax. It's largely just a parser. There are however some important points to note. In the current implementation, the assignment operator generally just copies the reference, rather than the value (it's the assignment operator of eval::val). This is true, unless you pass in an eval::val argument that uses eval::COPY_VALUE. Hence, for consistency, it is recommended that eval::COPY_VALUE not generally be used. Furthemore, when calling functions, pass by value/reference/pointer will be dealt with automatically. This means that if you wrap an int using eval::val (e.g. by passing in an int * to the constructor), it can be used for function calls involving int, int * and int& (and of course also double, const double&, ...). Hence, when registering overloads, it is recommended that if multiple overloads exist, which differ only in the way they receive their arguments (i.e. value, reference or pointer) only one of these be registered, to avoid eval::ambiguous_overload_exception. However, note that a function taking an int (by value) could be called with a double as well; the same would of course not be true for a function taking an int *.

Known Bugs and Limitations

TODO

Acknowledgements

Author

Christian Hoermann
Comments and suggestions appreciated ( 0oo0oo0 at gmail com ).

Generated on Sun Sep 21 14:39:37 2008 for eval by  doxygen 1.5.6