Skip to content

1. Introduction

Claude Roux edited this page Nov 29, 2020 · 42 revisions

Lisp

Lisp is one of the oldest programming languages in the world and yet despite its 60 years, it is far from retirement, especially thanks to its numerous descendants (see The Lisp family).

The version I propose here is rather rich and complete and is based on the syntax as John McCarthy had imagined it (see The Root of Lisp ).

You will find all the basic methods that made the language's heyday: cons, list, car, cdr, setq, defun, lambda, apply and so on, and some of the best (see LispE Help).

You will also find a rich collection of functions including:

Furthermore, the interpreter is multi-threaded.

Note: LispE is not the first programming language that we have implemented. The previous one: Tamgu is still available as open source at: GitHub Tamgu

Haskell Inspiration

We have also added functions inspired by Haskell: map, filter, zip, zipwith, takewhile, dropwhile...

These functions can be freely combined with each other in order to provide a lazy evaluation implementation.

LispE also provides data structures together with pattern matching declarations.

Note that historically, Lisp had a maplist operator, which is very similar to the map function that we provide here.

Moreover: you can use LispE as a real Shell scripting language.

We have added a bunch of functions to execute shell commands from a LispE script. You can even integrate it in a pipe in a very transparent way. (see Shell)

Above all, it can be modified at will.

As you will discover the structure of the interpreter is quite simple and can be easily modified. You will be able to add not only new instructions but also create your own dynamic libraries. We even provide a lisp program to generate an already compilable stub that you can modify at will.

You can derive, overload or enrich all the objects of the language...

Why a Lisp interpreter?

There is an important trend in designing systems that would automatically generate codes out of textual descriptions. For instance, GPT3 has been demonstrated as being able to learn how to code, thanks to thousands of examples it was feed with. However, if you compare Lisp to Python or Java or any other languages of that type, Lisp stands out with a very specific feature: Whatever the code you write, operators, functions and arguments will always fall in the same place. For instance, let's take an example in Python:

y = myfunction(10,20)
x = 10 + 20 - (y/2)

Now let's do the same in Lisp:

(setq y (myfunction 10 20))
(setq x (- (+ ( 10 20) (/ y 2))))

Hence, in Python, code mixes infix, prefix and postfix notations. For instance, operations are based on an infix notation where operators occur in the middle of the expression with the use of parentheses to explicit ambiguous cases.

In the case of Lisp, the pattern is the same whatever the instruction: always prefix.

This simplicity in patterns makes it quite interesting to automatically generate code as it is both regular and consistent across all code. Lisp code is hence much easier to learn than any other types of code.

Furthermore, we have put a lot of emphasis on usability. Our Lisp interpreter can be run from within Python for instance.

Interpreter

The interpreter has been entirely coded in C++11 and it exploits massively the notions of class inheritance as well as the STL templates proposed in C++11.

It does compile on most platforms: Windows, Mac OS and different versions of Linux (Centos, Fedora and Ubuntu).

The basic principle of our interpreter is the following:

All the elements of the language are implemented as classes derived from the class: Element

In this way, a list in Lisp can be implemented as a vector of Element .

Basic types

We also used the basic C++11 types to implement our different elements:

  1. The strings are implemented as std::wstring, which allows the language to directly manipulate Unicode strings.
  2. Lists are implemented as std::vector.
  3. Dictionaries are implemented as std::map and std::unordered_map.

As an example, a list is implemented in the following form:

class List : public Element {
public:
    std::vector<Element*> liste;
...

Since an Element can correspond to an Atom, a String or another List, it is understandable that such a representation allows recursively described lists.

Compilation and Execution

Compiling a Lisp program consists in creating an initial list in which we will put all the Element that the compiler will detect in the code.

As each class derived from Element overloads its own eval method, we just have to call eval on this initial list to launch the execution...

That's it, there is nothing more complicated than that...

Clone this wiki locally