Skip to content

2.3 Interpréteur Lisp

Claude Roux edited this page Oct 19, 2022 · 1 revision

Un interpréteur Lisp dans Tamgu

English Version

Lisp est le langage dont Dieu s'est servi pour créer le monde. Il est clair que cette citation est une exagération, car tout le monde sait que Dieu a créé le monde grâce au langage machine du Z80. Mais, peut-être a-t-il parfois implémenté certaines routines en Lisp. D'après certains programmeurs ayant une connaissance approfondie des textes sacrées, la structure des arbres et des fleurs ne peut provenir que de routines récursives, seules à même d'insuffler la fractalité au sein de la matière. Nous ne pouvons totalement rejeter cette idée fondamentale que Lisp est un langage dont Dieu a pu s'inspirer. Lisp est un langage dont la pureté ne cesse d'émerveiller le profane. Le formalisme de la majorité des langages force le compilateur à de complexes manipulations pour retrouver l'arbre syntaxique sous-jacent. La moindre expression mathématique camoufle sous des atours engageants sa véritable nature. Car en informatique tout programme est un arbre. "10+20*4" ne peut exister autrement que comme un graphe dont les feuilles sont des nombres et les noeuds des opérations. Lisp quant à lui ne connait que la liste et ses parenthèses sont là pour exprimer son arbre syntaxique. Ecrire en Lisp, c'est se mettre au diapason de la machine, lui parler directement dans son propre langage, même s'il y a beaucoup de parenthèses, beaucoup beaucoup de parenthèses. Mais c'est le prix à payer pour parler à la Machine.

Une BNF en 8 règles

En fait, ce qui rend Lisp particulièrement puissant, c'est aussi sa BNF, la grammaire qui décrit le langage.

Ce qui suit correspond aux règles du compilateur BNF créé spécifiquement pour Tamgu. Elles sont compilées en une classe C++: bnf_tamgu, où chaque règle correspond à une ou plusieurs méthodes de cette classe. Elles prennent en entrée le code déjà segmenté en sous-chaines.

tlvariable := wrong word [interval^indexes]
tlatom := predicatevariable^anumber^astringdouble^apreg^aspreg^atreg^astreg^tlvariable^word
%tlquote := %' [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]+

Il suffit de 8 règles pour décrire le langage au complet.

L'implémentation de Lisp

Non seulement, l'interprétation d'une expression Lisp ne requiert que quelques règles BNF, mais en plus le code complet pour compiler son arbre syntaxique et l'exécuter ne dépasse pas 2000 lignes de code C++ (voir codecompile.cxx et tamgulisp.cxx).

En effet, l'arbre syntaxique engendre directement l'arbre d'exécution...

Comme pour l'ensemble des objets dans Tamgu, la méthode la plus lourde à surcharger est Eval (voir Functional Properties of Objects. Et c'est là que Tamgu rejoint le divin. (hem... les formulaires pour l'adhésion à la secte ne sont pas encore prêts). Enfin, rejoint Lisp... En effet, cette méthode Eval, la partie la plus lourde de l'interpréteur Lisp dans Tamgu (voir tamgulisp.cxx) est le reflet exact de la fonction eval de Lisp. Il s'agit d'un immense switch/case dont le test porte sur le premier élément d'une expression. Ainsi, l'analyse de (cons 'a '(b c)) consiste à exécuter le code correspondant à l'entrée de cons dans Eval.

La seule déviation par rapport au Lisp traditionnel est l'utilisation d'un "\" devant la première parenthèse comme indice de compilation.

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

Comme le montre cet exemple, toutes les méthodes et les procédures de Tamgu peuvent être appelées directement dans une expression Lisp. Dans le cas d'une méthode, l'objet associé est le premier argument de l'expression.

Langage Lisp

L'ensemble des opérateurs et fonctions traditionnelles de Lisp sont présents: append, cons, cond, car, cdr, defun, eval, lambda, label, list, self, setq etc.

Voici comment on peut calculer la factorielle de 6 en une ligne de code et ranger le résultat dans une variable:

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

Non seulement, on peut directement assigner le résultat d'une expression Lisp à une variable Tamgu, mais on peut aussi prendre une variable Tamgu et la donner en entrée à une expression Lisp:

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

Le résultat est: [3,4,5,6,7,8,9,10]

Enfin, lorsque l'on déclare une fonction Lisp, on peut l'utiliser à toutes les sauces dans Tamgu:

\(defun appel(x y)
    (* x
        (- 1 y)
    )
)

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

Evidemment, la méthode eval peut aussi être appelée et dans ce cas, elle peut analyser une expression et l'exécuter:

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

Ce qui donnera comme résultat: (a b c d e)

Type: lisp

Enfin, Tamgu fournit aussi un type: lisp dont la méthode eval prend une chaine de caractères en entrée:

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

Ce qui donnera comme résultat: (a b c)

De plus, ce type peut prendre un vecteur en entrée et le transformer en une liste Lisp:

ivector iv=[1..10];
lisp l = iv;
  • L'affichage de iv donne: [1,2,3,4,5,6,7,8,9,10]
  • L'affichage de l donne: (1 2 3 4 5 6 7 8 9 10)

_map et _filter

Le lisp de Tamgu offre deux méthodes particulières pour appliquer des opérations sur une liste.

  • _map applique un opérateur ou une lambda sur une liste d'éléments
  • _filter filtre les données d'une liste via une comparaison ou une lambda
(_map '+ '(1 2 3 4))			renvoie (2 4 6 8)
(_map '(- 1) '(1 2 3 4))			renvoie (0 1 2 3)
(_map '(1 -) '(1 2 3 4))			renvoie (0 -1 -2 -3)
(_map (lambda (x) (* x 3)) '(1 2 3 4))	renvoie (3 6 9 12)

(_filter (< 3) '(1 2 3 4))			renvoie (1 2)
(_filter (lambda(x) (< x 4)) '(1 2 3 4)) 	renvoie (1 2 3)

Entorse à la pureté du langage: Dictionnaires

Dans la version que nous avons implémentée, nous avons rajouté le support direct des dictionnaires, en permettant l'utilisation des {} dans le code directement, en revanche, à la différence du reste de Tamgu, les clef/valeurs doivent être séparées par un espace. C'est du Lisp quand même...

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

L'exemple ci-dessus prend un dictionnaire en entrée et renvoie la valeur dont la clef est "c".

Pure Lisp

L'obligation d'ajouter un "\" devant chaque expression Lisp peut rendre le code quelque peu illisible. Il est possible de créer des fichiers dans lequel le code ne contient que du Lisp. Il suffit pour cela que les deux premiers caractères du fichier soit: (). La suite n'est dès lors que du Lisp.

()

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

(println (appel 10 30))

On peut aussi utiliser _lispmode() dans la console pour déclencher ce mode. Pour sortir de ce mode, il faut évidemment rappeler cette instruction sous la forme d'une expression Lisp: (_lispmode false).

Clone this wiki locally