-
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: Add in LispE::initialization (see lispe.cxx) a new line: instructions [l_new] = "new";
- Add a new case in List::eval (in elements.cxx):
(in lispe.cxx)
instructions[l_new] = "new";
...
(in eval.cxx: List::eval(LispE* lisp) )
case l_new: {
//enter your code then
}
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 definition from pattern function definition:
(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.