Skip to content

3.1 How to enrich LispE

Claude Roux edited this page Dec 3, 2020 · 8 revisions

Enrich LispE

There are three ways to enrich LispE:

  1. You can create your own basic instructions and integrate them into List::eval.
  2. You can create an extension (see maths.cxx, strings.cxx, system.cxx, random.cxx)
  3. You can create a dynamic library (see: Dynamic Library)

Add your instructions

Adding a new instruction is a three-step process:

  1. Add a new identifier in lisp_code (in elements.h). Just add an additional name such as l_new.
  2. Associate this identifier with an instruction name, an arity and the method that will be executed. This declaration is done in lispe.cxx, in the method: void Delegation::initialisation(LispE* lisp).
  3. This method should be declared in `class List, in elements.h.

Example

We want to create a new instruction: plusplus, which increments a variable by one.

  1. First, add: l_plusplus to lisp_code in elements.h:

IMPORTANT be sure to place this new value before l_final in the structure...

 l_plusplus, l_plus, l_minus, l_multiply, l_power,
  1. Second, add the following line in Delegation::initialisation(LispE* lisp):
    set_instruction(l_plusplus, "plusplus", P_TWO, &List::evall_plusplus);
  1. Third, add the declaration of evall_plusplus in elements.h in the class: List:
//The signature is very simple, it only takes a LispE object...
Element* evall_plusplus(LispE*);
  1. Finally, add the body of the function in eval.cxx:
//Note that we need to initialise some context, use the other eval functions as examples:
Element* List::evall_plusplus(LispE* lisp) {
    //plusplus requires two arguments: the first argument is the instruction name itself
    if (liste.size() != 2)
        throw new Error("Error: wrong number of arguments");
    
    //To handle the trace or debugger calls
    lisp->display_trace(this);

    //our  variable is the second element of the list: (plusplus var)
    Element* element = liste[1];
    //The value should be a variable 
    short label = element->label();
    if (label < l_final)
       throw new Error("Error: 'plusplus' requires a variable");
    

    //The try catch is here to release 'element' in case of error
    try {
        //copyatom provides a copy of an Element object (string, number or integer)
        //when this object is a constant. Otherwise, it returns the object itself
        //for modification.
        element = element->eval(lisp)->copyatom(s_constant);
        //We increment it
        element->plus(lisp, one_);
        //We then record this value back into memory        
        return lisp->recording(element, label);
    }
    catch (Error* err) {
        //if an error occurred for element->eval(lisp), we need to be sure
        //that element is properly cleaned...
        element->release();
        throw err;
    }

    return null_;
}

That's it...

Add an extension

The creation of an extension consists in creating a new class that derives from the Element class.

This class must overload at least: eval and asString.

class MyClass : public Element {
public:

     MyClass: Element(l_lib) {..}

     Element* eval(LispE* lisp) {
          //your code
     }

     wstring asString(LispE* lisp) {
        //two cases:
        a) It is a class that only exports instructions
        b) It is an object of type _data_.
     }
};

Association

Once you have created your class, you need to associate an instance of this class with a LispE method, which is done by calling:

    lisp->extension("deflib function_name (p1 p2 p3)", new MyClass);

deflib provides not only the name of the function, as it will be known in programs, but also a list of parameters whose names are very important. Indeed, they are the ones that will allow the eval method to access the arguments transmitted during the call.

  
Element* eval(LispE* lisp) {
    Element* p1 = lisp->get(L"p1");   
    Element* p2 = lisp->get(L"p2");
    Element* p3 = lisp->get(L"p3");
...
}

Just call the method: get with the name of the parameter to retrieve the corresponding argument... As this value is stored in the execution stack, you don't need to release them.

Data/Instructions

Before reading the next paragraphes, we suggest that you read the chapter on data structures. This chapter is actually the reason we have implemented this dichotomy between data and functions.

Basically, in LispE, we differentiate data definitions from pattern function definitions:

(data MyData (D1 _ _) (D2 _ _))

(defpat MyFnc ( (D1 x y)) ...)

On the one hand, we have the data structure that we want to implement and on the other hand, we have the way these data structures should be handled. However, both are actually implemented as LispE functions.

This is the reason why LispE exposes two ways to implement extensions:

  1. Your extension only exports instructions
  2. Your extension is a data structure

In both cases, it is a derivation of the Element class, since these structures are both underlying LispE functions.

Hence, if you want to implement a data structure, you will need two classes.

  • One class to handle the data structures
  • One class to handle pattern matching functions on top of these data structures

Most extensions export only instructions (see maths.cxx for an example).

However, it is not always the case: the Date implementation, for instance, has been implemented as two classes:

  • One class to store a date value (Dateitem in systems.cxx))
  • One class to expose the methods to handle these data value (Date in systems.cxx)

Example:

class Dateitem : public Element {
public:
  
    time_t the_time;
    
    Dateitem(short letype) : Element(letype) {
        time(&the_time);
    }

    wstring asString(LispE* l) {
        wstring s;
        char buffer[100];
        long sz;
        struct tm* ladate = localtime(&the_time);
        sz = strftime(buffer, 100, "%Y/%m/%d %H:%M:%S", ladate);
        for (long i = 0; i < sz; i++)
            s+= (wchar_t)buffer[i];
        return s;
    }

};

We then have the Date class, which can create new date values at will:

class Date : public Element {
public:

    tempus tmp;
    short v_d;
    short v_date;
    
    Date(LispE* lisp, short l_date, tempus t) : tmp(t),  Element(l_date) {
        wstring s(L"d");
        v_d = lisp->encode(s);
        s = L"adate";
        v_date = lisp->encode(s);
    }

    //the eval method that evaluate, which instruction is called and executed
    Element* eval(LispE* lisp) {
        switch (tmp) {
            case date_raw: {
                //We create a new Dateitem
                return new Dateitem(type);
            }
            case date_year: {
                return year(lisp);
            }
...


    wstring asString(LispE* lisp) {
        switch (tmp) {
            case date_setdate: {
                return L"setdate(y,m,d,H,M,S): Creates a date from its compasantes (-1 for the default value)";
            }
            case date_year: {
                return L"year(int d): Returns the year (d = -1) or modifies it";
            }
            case date_month: {
                return L"month(int d): Returns the month (d = -1) or modifies it";

Note the way asString is defined as a description of each instruction, while in Dateitem asString is implemented to return the date as a string value.

Each instruction is created via a call to extension, which records a deflib description:

    lisp->extension("deflib date ()", new Date(lisp, identifier,  date_raw));

   //Note that "d" is provided as a list, to indicate that it is optional
    //If the list consists of two items, it means that the second item is the default value
    lisp->extension("deflib year (adate (d -1))", new Date(lisp, identifier,  date_year));
    lisp->extension("deflib month (adate (d -1))", new Date(lisp, identifier,  date_month));
    lisp->extension("deflib day (adate (d -1))", new Date(lisp, identifier,  date_day));
    lisp->extension("deflib hour (adate (d -1))", new Date(lisp, identifier,  date_hour));
    lisp->extension("deflib minute (adate (d -1))", new Date(lisp, identifier,  date_minute));
    lisp->extension("deflib second (adate (d -1))", new Date(lisp, identifier,  date_second));
    lisp->extension("deflib yearday (adate)", new Date(lisp, identifier,  date_yearday));
    lisp->extension("deflib weekday (adate)", new Date(lisp, identifier,  date_weekday));

Hence, each instruction is implemented as one single instance of the class: Date. The first description is actually the call to date itself:

    lisp->extension("deflib date ()", new Date(lisp, identifier,  date_raw));

which, when executed will create a Dateitem object.

The distinct implementation of Date and Dateitem reflects this idea of separating data definition from function applications.

Clone this wiki locally