-
Notifications
You must be signed in to change notification settings - Fork 8
3.1 How to enrich LispE
There are three ways to enrich LispE:
- You can create your own basic instructions and integrate them into List::eval.
- You can create an extension (see maths.cxx, strings.cxx, system.cxx, random.cxx)
- You can create a dynamic library (see: Dynamic Library)
Adding a new instruction is a three-step process:
- Add a new identifier in lisp_code (in elements.h). Just add an additional name such as l_new.
- 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)
. - This method should be declared in `class List, in elements.h.
We want to create a new instruction: plusplus
, which increments a variable by one.
- First, add:
l_plusplus
tolisp_code
inelements.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,
- Second, add the following line in
Delegation::initialisation(LispE* lisp)
:
set_instruction(l_plusplus, "plusplus", P_TWO, &List::evall_plusplus);
- Third, add the declaration of
evall_plusplus
inelements.h
in the class: List:
//The signature is very simple, it only takes a LispE object...
Element* evall_plusplus(LispE*);
- 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...
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_.
}
};
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.
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:
- Your extension only exports instructions
- 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.