Skip to content

2.3 Lisp Interpreter

Claude Roux edited this page Oct 19, 2022 · 2 revisions

A Lisp interpreter in Tamgu

Version française

Lisp is the language God used to create the world. It is clear that this quote is an exaggeration, because everyone knows that God created the world thanks to the machine language of the Z80. But, perhaps he sometimes implemented certain routines in Lisp. According to some programmers with a deep knowledge of sacred texts, the structure of trees and flowers can only come from recursive routines, the only ones capable of instilling fractality into matter. We cannot totally reject the fundamental idea that Lisp is a language from which God was able to draw inspiration. Lisp is a language whose purity never ceases to amaze the layman. The formalism of most languages forces the compiler to complex manipulations to find the underlying syntactic tree. The slightest mathematical expression camouflages its true nature. Because in computing, every program is a tree. "10+20*4" can only exist as a graph whose leaves are numbers and whose nodes are operations. Lisp knows only the list and its parentheses are there to express its syntactic tree. Writing in Lisp is to get in tune with the machine, to speak to it directly in its own language, even if there are many parentheses, many many parentheses. But that's the price to pay for talking to the Machine.

A BNF in 8 rules

In fact, what makes Lisp particularly powerful is also its BNF, the grammar that describes the language.

The following is the rules of the BNF compiler created specifically for Tamgu. They are compiled into a C++ class: bnf_tamgu, where each rule corresponds to one or more methods in that class. They take as input the code already tokenized.

tlvariable := wrong word [interval^indexes]
tlatom := predicatevariable^anumber^astringdouble^apreg^aspreg^atreg^astreg^tlvariable^word
%quote := %' [tlquote^tlatom^comparator^operator^tlkeys^tlist]
%tlkey := tlatom %: [tlquote^tlatom^tlkeys^tlist]
%tlkeys:= %{ ;26 tlkey+ %}
%tlist := %( ;67%)^[(operator^comparator) [tlquote^tlatom^tlkeys^tlist]+ (operator) %)]
%tamgulisp:= %\ [tlquote^tlatom^tlkeys^tlist]
%tamgupurelisp := [tlquote^tlatom^tlkeys^tlist]+

Only 8 rules are needed to describe the entire language.

The implementation of Lisp

Not only does the interpretation of a Lisp expression require only a few BNF rules, but the complete code to compile its syntax tree and execute it does not exceed 2000 lines of C++ code (see codecompile.cxx and tamgulisp.cxx).

Indeed, the syntax tree directly begets the execution tree...

As for all the objects in Tamgu, the heaviest method to overload is Eval (see Functional Properties of Objects). And this is where Tamgu joins the divine. (hem... the forms for cult membership are not ready yet). Finally, Tamgu reaches to Lisp... Indeed, this Eval method, the main method of the Lisp interpreter in Tamgu (see tamgulisp.cxx) is an exact reflection of Lisp's eval function. It is a huge switch/case that triggers on the first element of an expression. Thus, the analysis of (cons 'a '(b c)) consists in executing the code corresponding to the entry of cons in Eval.

The only deviation from the traditional Lisp is the use of a "\" before the first parenthesis as a compiler cue.

\(println (cons 'a '(b c d e))) //Procedure Tamgu + Pure Lisp
\(println (cdr (range 1 100 1))) //Procedure Tamgu + Pure Lisp
\(println (split "ab,cd,ef" ",")) //Method Tamgu
\(println (log 1.234)) //Method Tamgu

As this example shows, all Tamgu's methods and procedures can be called directly in a Lisp expression. In the case of a method, the associated object is the first argument of the expression.

Lisp language

All of Lisp's traditional operators and functions are present: append, cons, cond, car, cdr, defun, eval, lambda, label, list, self, setq etc.

Here is how you can calculate the factorial of 6 in one line of code and put the result in a variable:

 int fact = \( (lambda (x) (if (eq x 1) 1 (* x (self (- x 1)) ) ) ) 6 );

Not only can we directly assign the result of a Lisp expression to a Tamgu variable, but we can also take a Tamgu variable and give it as input to a Lisp expression:

ivector iv=[1..10];
\(println (cddr iv))

The result is: [3,4,5,6,7,8,9,10]

Finally, when you declare a Lisp function, you can use it the way you want in Tamgu:

\(defunct call(x y))
    (* x
        (- 1 y)
    )
)

\(println (call 10 30)) //Expression Lisp
println(call(10,20)); //Tamgu expression
println(<call 50 20>); //Expression Haskell

Of course, the eval method can also be called and in this case it can parse an expression and execute it:

\(println (eval (list 'cons ''a '''(b c d e)))

The result is then: (a b c d e)

Type: lisp

Finally, Tamgu also provides a type: lisp whose eval method takes a string as input:

string code="(cons 'a '(b c))";
lisp l; 
println(l.eval(code));

Which will result in: (a b c)

In addition, this type can take a vector as input and transform it into a Lisp list:

ivector iv=[1..10];
lisp l = iv;
  • The display of iv yields: [1,2,3,4,5,6,7,8,9,10]
  • The display yields: (1 2 3 4 5 6 7 8 9 10)

_map and _filter

Tamgu lisp offers two particular methods for applying operations to a list.

  • _map applies an operator or a lambda to a list of elements
  • _filter filters data from a list via a comparison or a lambda
(_map '+ '(1 2 3 4))                            //returns (2 4 6 8)
(_map '(- 1) '(1 2 3 4))                        //returns (0 1 2 3)
(_map '(1 -) '(1 2 3 4))                        //returns (0 -1 -2 -3)
(_map (lambda (x) (* x 3)) '(1 2 3 4))          //returns (3 6 9 12)

(_filter (< 3) '(1 2 3 4))                      //returns (1 2)
(_filter (lambda(x) (< x 4)) '(1 2 3 4))        //returns (1 2 3)

Breach of language purity: Dictionaries

In the version we implemented, we added dictionary support, allowing the use of {} in the code directly, however, unlike the rest of Tamgu, keys/values must be separated by a space. This is Lisp anyway...

\(key {"a":2 "b":3 "c":10} "c")

The above example takes a dictionary as input and returns the value whose key is "c".

Pure Lisp

The requirement to add a "\" before each Lisp expression can make the code somewhat unreadable. It is possible to create files in which the code contains only Lisp. All that is needed is to add two characters to the very beginning of a file: (). The rest is then only Lisp.

()

(defun call(x y)
    (* x
        (- 1 y)
    )
)

(println (call 10 30))

You can also use _lispmode() in the console to trigger this mode. To get out of this mode, you obviously need to recall this instruction as a Lisp expression: (_lispmode false).

Clone this wiki locally