home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
messroms.de
/
2007-01-13_www.messroms.de.zip
/
ORIC1
/
IMAGES
/
ORICLISP.ZIP
/
manuel.txt
< prev
next >
Wrap
Text File
|
1995-11-20
|
60KB
|
1,680 lines
Manuel de rΘfΘrence ORICLISP
(C) F.FrancΦs 1986,1995
Table des matiΦres
I. Une introduction au langage LISP................................ 2
I.1 Syntaxe...................................................... 3
I.2 L'interprΦte................................................. 3
I.3 Les fonctions prΘdΘfinies.................................... 4
a) QUOTE................................................... 4
b) Les sΘlecteurs CAR et CDR............................... 4
c) Les constructeurs....................................... 5
d) Les prΘdicats........................................... 5
e) La conditionnelle et les structures de contr⌠le......... 6
f) L'arithmΘtique.......................................... 6
g) L'affectation........................................... 6
h) La dΘfinition de fonctions.............................. 7
II. PrΘsentation d'OricLisp........................................ 8
II.1 Principales caractΘristiques................................ 8
II.2 Premier contact............................................. 8
a) L'Θcran et le clavier................................... 9
b) L'Θdition de lignes..................................... 9
II.3 Les primitives OricLisp..................................... 10
a) QUOTE................................................... 10
b) Les sΘlecteurs.......................................... 10
c) Les constructeurs....................................... 11
d) Les prΘdicats........................................... 12
e) La conditionnelle et les structures de contr⌠le......... 13
f) L'arithmΘtique.......................................... 14
g) Les modifications physiques
et les fonctions α effet de bord...................... 15
h) La dΘfinition de fonction............................... 17
II.4 Concepts avancΘs............................................ 18
a) Fonctions sans Θvaluation............................... 18
b) Macro-caractΦres........................................ 19
c) Macro-fonctions......................................... 19
d) Fonctions d'ordre supΘrieur............................. 21
A. Annexe A: Copyright............................................. 21
B. Annexe B: DΘfinitions formelles................................. 22
C. Annexe C: Portage de programmes MuLisp.......................... 24
D. Annexe D: DΘtails d'implΘmentation.............................. 26
a) Carte mΘmoire........................................... 26
b) Structures de donnΘes................................... 26
c) La pile virtuelle....................................... 27
d) Le ramasse-miettes...................................... 27
E. Annexe E: Exemple............................................... 28
- 1 -
I. Une introduction au langage LISP
-----------------------------------
LISP est un langage crΘΘ en 1964 par John MacCarthy pour traiter des
informations organisΘes en arbres binaires (formules mathΘmatiques, phrases de
la langue naturelle, connaissances...). Ces arbres sont reprΘsentΘs par des
listes en LISP (qui est une abrΘviation de LISt-Processing).
La capacitΘ de LISP α traiter des informations symboliques en a fait
un des "langages machine" de domaines de l'informatique tels que l'Intelligence
Artificielle, la Robotique, le Calcul Formel, la ThΘorie de la Programmation
(les preuves de programme par exemple), la thΘorie des jeux, les Θditeurs
syntaxiques (type Emacs) et plus gΘnΘralement les environnements de programma-
-tion, etc.
Pour le situer par rapport aux autres langages, on a l'habitude de
considΘrer trois familles de langages (certains langages sont bien s√r α
l'intersection de ces familles): les langages impΘratifs comme Fortran, Pascal,
Cobol, Basic, Ada, C... o∙ une suite d'ordres permet de converger vers la
solution; les langages dΘclaratifs comme Prolog o∙ l'on se contente de dΘcrire
le problΦme (c'est une logique dΘductive qui permet d'aller vers la solution);
les langages fonctionnels et applicatifs comme LISP, ML, FP o∙ l'on applique
des fonctions au sens mathΘmatique du terme (il n'y a plus de "variables" dans
le programme). On peut considΘrer que les premiers langages α objets comme
SmallTalk descendent de LISP, et on trouve aussi son influence dans le langage
LOGO.
Hormis cette classification, les principales caractΘristiques des
langages fonctionnels de la famille LISP sont les suivantes (il n'y a en effet
pas un mais des dialectes LISP: InterLisp, MacLisp, VLisp, LeLisp, MuLisp,
FranzLisp...):
- ils procΦdent par composition de fonctions rΘcursives alors que les
langages classiques (impΘratifs) procΦdent par rΘpΘtition de sΘquences
d'affectation.
- ils manipulent des arborescences et un programme est un arbre de mΩme
nature qu'un arbre de donnΘes (un programme peut donc Ωtre traitΘ par un autre
programme !)
- ils sont structurΘs et modulaires (par composition de fonctions)
- ils sont interactifs (le dialogue avec l'interprΦte permet de
construire les programmes de maniΦre incrΘmentale)
- la gestion de la mΘmoire est dynamique et automatique (grΓce α un
"ramasse-miettes" qui rΘcupΦre les donnΘes qui ne sont plus utilisΘes)
- 2 -
I.1 Syntaxe
-----------
Un programme LISP est un arbre binaire, soit, comment donc le
reprΘsenter avec une suite de caractΦres ASCII ? Bien s√r, il n'est pas question
de faire un dessin, alors il a ΘtΘ choisi d'utiliser les parenthΦses, l'espace
et le point pour reprΘsenter ces arborescences. DΦs que l'on aura remarquΘ que
les deux branches d'un arbre binaire sont elles-mΩmes des arbres, on acceptera
sans peine la reprΘsentation suivante dite "paire pointΘe": ( S1 . S2 )
o∙ S1 et S2 sont aussi soit des paires pointΘes soit des feuilles de l'arbre,
que l'on appelle des "atomes".
Les atomes, comme en physique, sont donc les ΘlΘments constitutifs et
deux sortes sont distinguΘes : les nombres entiers (parfois aussi des rΘels) et
les "symboles" qui sont des identificateurs et jouent le r⌠le de donnΘes
abstraites. Par exemple, deux symboles sont utilisΘs pour reprΘsenter les
boolΘens classiques : NIL pour "Faux" et T pour "Vrai" (bien que tout ce qui
n'est pas NIL est aussi "Vrai"). De plus, NIL sert aussi pour dΘnoter le "Vide",
la liste vide plus prΘcisΘment...
Puisque nous Θvoquons le terme de "liste", prΘcisons que les listes sont
des arbres particuliers (les arbres sont appelΘs "S-expressions" en LISP) : il a
ΘtΘ choisi de faire porter le premier ΘlΘment d'une liste par la partie gauche
de la paire pointΘe et la suite de la liste par la partie droite, ce qui fait
qu'une liste de fruits s'Θcrirait, α l'aide de paires pointΘes :
( pomme . ( poire . ( abricot . nil ) ) )
Les listes sont tellement courantes que la notation ci-dessus est peu
pratique et l'on a bien s√r une premiΦre Θcriture abrΘgΘe :
( pomme poire abricot . nil )
C'est une simplification d'Θcriture qui donne une signification spΘciale
α la sΘparation par des espaces α l'intΘrieur de parenthΦses. On abrΦge encore
cette Θcriture en donnant la signification de fin de liste au symbole NIL avec :
( pomme poire abricot )
Voilα qui est bien plus simple ! Mais pour compliquer un peu, rappelons
qu'une liste est une S-expression; rien n'empΩche donc un ou des ΘlΘments d'une
liste d'Ωtre eux-mΩmes des listes... Par exemple,
( marcel (age 20) (enfants (lucie (age 2)) (alain (age 1)) ) )
est une liste de 3 ΘlΘments ! Le premier est un atome, le second une liste de
deux atomes, le troisiΦme une liste d'un atome et de deux listes... on commence
α comprendre que savoir compter les parenthΦses va Ωtre important !
I.2 L'interprΦte
----------------
Comment ? On change de paragraphe sans donner la syntaxe des programmes
LISP ? Eh oui, si vous savez construire des listes, vous savez programmer !
Un programme LISP, c'est une S-expression, disons plut⌠t un atome ou une liste,
que l'interprΦte va "Θvaluer". Par exemple, si vous donnez un nombre α Θvaluer
α l'interprΦte, il vous renverra ce mΩme nombre (facile...). Si vous donnez un
symbole α Θvaluer, l'interprΦte retournera la "valeur" de ce symbole s'il en a
une, par exemple : MON_DOCTEUR pourrait s'Θvaluer en
(Dugenou Leon (23 chemin du cubitus))
Enfin, si vous donnez une liste α Θvaluer, l'interprΦte considΦrera que le
premier ΘlΘment de la liste est une fonction et que les autres ΘlΘments sont les
arguments donnΘs α la fonction. Par exemple :
(+ 2 2) renverra 4
(CAR MON_DOCTEUR) renverra Dugenou, on verra en effet plus loin que CAR
est une fonction qui renvoie le premier ΘlΘment d'une liste.
- 3 -
L'interprΦte LISP peut donc Ωtre dΘcrit par l'application successive des
trois fonctions suivantes :
- READ qui lit une S-expression (nous dirons par la suite une expression pour
simplifier) au clavier ou depuis un fichier.
- EVAL qui Θvalue la valeur d'une expression.
- PRINT qui affiche une expression sur l'Θcran ou dans un fichier.
Selon le dialecte LISP utilisΘ, cet interprΦte peut Ωtre une fonction standard
(TOPLEVEL ou DRIVER ou autre), dont la dΘfinition contient une composition des
trois fonctions prΘcΘdentes : (PRINT (EVAL (READ)))
On remarque que les arguments Θtant ΘvaluΘs avant d'Ωtre passΘs aux fonctions,
c'est la fonction READ qui est invoquΘe d'abord, son rΘsultat est passΘ α la
fonction EVAL, et le rΘsultat d'EVAL est envoyΘ α PRINT ! (on verra que la
plupart des fonctions Θvaluent leurs arguments, c'est l'appel par valeur, seules
quelques fonctions ne les Θvaluent pas, comme les structures de controles, on
parle alors d'appel par nom).
C'est donc la fonction EVAL qui est au coeur du langage LISP: lorsque
vous demandez (+ 2 2), c'est elle qui invoquera le code de la fonction addition.
I.3 Les fonctions prΘdΘfinies
-------------------------
Tous les dialectes LISP partagent un mΩme noyau minimal de fonctions qui
est suffisant pour Θcrire n'importe quel programme. Les dialectes se diffΘren-
-cient par des jeux de fonctions Θtendus, plus ou moins riches, qui permettent
d'Θcrire les programmes de maniΦre plus concise. Nous dΘcrivons ici un noyau
minimal:
a) QUOTE
--------
QUOTE est une fonction qui permet de protΘger son argument de l'Θvalua-
-tion. Elle n'Θvalue donc pas son argument et le renvoie tel quel en rΘsultat.
Exemple:
(QUOTE (+ 2 2)) --> (+ 2 2)
Son usage trΦs courant fait qu'une abrΘviation a ΘtΘ dΘfinie :
'A --> A
'(CAR MON_DOCTEUR) --> (CAR MON_DOCTEUR)
Pour les curieux, le caractΦre ' est un "macro-caractΦre", c.a.d que c'est en
fait une fonction qui s'Θvalue dΦs qu'elle est rencontrΘe dans le flot de
caractΦres venant du clavier (l'action du macro-caractΦre ' est d'invoquer la
fonction READ pour lire l'expression suivante, puis de construire une liste avec
QUOTE en premier ΘlΘment et cette expression en second ΘlΘment, et enfin de
renvoyer cette liste !)
b) Les sΘlecteurs CAR et CDR
----------------------------
Ces fonctions Θvaluent leur argument, CAR prend la partie gauche de la
paire pointΘe donnΘe en argument et CDR la partie droite (les noms de ces
fonctions viennent hΘlas de l'appellation A et D de registres de la premiΦre
machine ayant implΘmentΘ LISP...)
Exemple:
(CAR '(+ 2 3)) --> +
(CAR '((A B) C)) --> (A B)
(CDR '(+ 2 3)) --> (2 3)
(CDR '((A B) C)) --> (C)
- 4 -
Suivant les dialectes LISP, appliquer la fonction CAR α un atome peut renvoyer
une erreur ou la valeur de cet atome. De mΩme, la fonction CDR appliquΘe α un
atome peut retourner une erreur ou une liste de propriΘtΘs associΘes α l'atome
(ces deuxiΦmes possibilitΘs permettent d'appliquer CAR et CDR α n'importe quelle
expression LISP, mais il vaut mieux rΘserver ces fonctions aux paires pointΘes
(listes ou arbres) par souci de portabilitΘ).
c) Les constructeurs
--------------------
CONS est la fonction constructeur de base : elle construit une paire
pointΘe α partir de deux expressions.
Exemple:
(CONS 'A 'B) --> (A.B)
(CONS 'A '(B C)) --> (A B C)
(CONS '(A B) '(C)) --> ((A B) C)
(CONS 'A NIL) --> (A)
(CONS NIL '(A)) --> (NIL A)
LIST existe toujours pour simplifier la construction de listes de longueur
quelconque. La fonction LIST accepte un nombre quelconque d'arguments.
Exemple:
(LIST 'a 'b 'c 'd) --> (a b c d)
(LIST 'a '(b c) 'd) --> (a (b c) d)
d) Les prΘdicats
----------------
Ce sont des fonctions renvoyant une valeur boolΘenne (NIL reprΘsente la
valeur "Faux", tout autre rΘsultat est "Vrai"), elles Θvaluent leurs arguments.
ATOM renvoie T si son argument est un atome, sinon NIL.
NUMBERP renvoie T si son argument est un nombre, sinon NIL.
CONSP renvoie T si son argument est une liste, sinon NIL.
NULL renvoie T si son argument est NIL, sinon NIL.
EQUAL renvoie T si ses arguments sont Θgaux, sinon NIL.
EQ renvoie T si ses arguments sont les mΩmes, sinon NIL.
Ces deux derniers mΘritent une petite explication: EQ teste si les deux objets
donnΘs en argument ne font qu'un en mΘmoire (ΘgalitΘ physique) tandis que EQUAL
compare toutes les branches des objets pour vΘrifier qu'ils ont mΩme reprΘsenta-
-tion externe.
Exemple:
(ATOM 'A) --> T
(ATOM '(A)) --> NIL
(ATOM '()) --> T en effet, () est la liste vide reprΘsentΘe par
l'atome NIL !
(NULL 'A) --> NIL
(NULL '(A)) --> NIL
(NULL NIL) --> T
(EQUAL '(A (B C) D) '(A (B C) D)) --> T
(EQ '(A (B C) D) '(A (B C) D)) --> NIL
(EQUAL 'A 'A) --> T
(EQ 'A 'A) --> T car les atomes sont prΘsents de maniΦre unique
en mΘmoire...
- 5 -
e) La CONDitionnelle
--------------------
COND accepte en paramΦtre un nombre quelconque de clauses (non ΘvaluΘes)
de la forme "(prΘdicat expression)". COND n'Θvalue pas ses arguments α priori,
mais Θvalue sΘquentiellement les prΘdicats dans les clauses jusqu'α en trouver
un "Vrai" (non NIL), dans ce cas il Θvalue l'expression associΘe dans la clause
et la renvoie en rΘsultat. Sinon (tous les prΘdicats ont ΘtΘ ΘvaluΘs α NIL),
COND renvoie NIL.
Exemple:
(COND ((EQ 'A 'A) 'OUI)) --> OUI
(COND ((EQ 'A 'B) 'OUI)) --> NON
(COND ((EQ 'A 'B) 'OUI)
(T 'NON)) --> NON
ce qui montre l'utilisation de T comme dernier prΘdicat,
mais on peut s'en passer comme suit :
(COND ((EQ X 1) 'PREMIER)
((EQ X 2) 'DEUXIEME)
((EQ X 3) 'TROISIEME)
( 'AUTRE)) --> PREMIER, DEUXIEME, TROISIEME ou AUTRE
suivant la valeur de X...
f) L'arithmΘtique
-----------------
Les noms de fonction varient d'un dialecte α un autre, mais on trouvera
toujours les 4 opΘrations et des prΘdicats d'ordre.
Exemple:
(- 239 (* 7 (/ 239 7))) --> 1
(< 3 7) --> T
g) L'affectation
----------------
Pour les puristes, c'est une horreur comme un GOTO dans un langage
structurΘ. En pratique, on rΘserve souvent son usage au niveau le plus haut d'un
programme pour associer un nom α une valeur, comme on peut le faire en mathΘma-
-tique, mais on se garde de modifier cette association (les symboles ne sont
pas des variables !) pour Θviter les effets de bord.
Exemple:
(SETQ A '(1 2 3)) --> (1 2 3)
A --> (1 2 3)
SETQ n'Θvalue pas son premier argument (symbole), et renvoie son second argument
aprΦs l'avoir associΘ au symbole. SET est encore plus dangereux car il Θvalue
aussi le premier argument:
(SETQ B 'A) --> A
B --> A
(SET B '(A B)) --> (A B)
B --> A
A --> (A B)
- 6 -
h) La dΘfinition de fonctions
-----------------------------
Une fonction LISP est une expression de la forme
(LAMBDA (param1 param2 ... paramN) expr)
o∙ param1, param2 ... paramN sont les noms des paramΦtres formels et expr le
corps de la fonction.
Exemple:
(LAMBDA (X) X) est la fonction identitΘ (elle renvoie son argument)
(LAMBDA (X Y)
(COND ((ATOM X) (CONS X Y))
(T (CONS (CAR X) Y))))
est une fonction qui rajoute X en tΩte de la liste Y si
X est un atome, sinon rajoute le CAR de X en tΩte de la
liste Y.
La maniΦre d'associer une dΘfinition de fonction α un nom n'est pas standard,
LeLisp utilise la fonction DE (par exemple: "(DE IDENTITE(X) X)" ), VLisp range
la dΘfinition dans la liste de propriΘtΘ d'un atome, d'autres associent la
dΘfinition α la valeur d'un atome. Pour la suite, nous utiliserons la fonction
MuLisp PUTD.
Exemple:
(PUTD 'IDENTITE '(LAMBDA(X) X))
En MuLisp, les atomes ont par dΘfaut pour valeur eux-mΩmes, ce qui permet de
supprimer l'apostrophe (quote) devant le nom de fonction lorsque aucune valeur
ne lui est associΘe. Allez, quelques classiques :
(PUTD LENGTH
'(LAMBDA (X)
(COND ((NULL X) 0)
(T (+ 1 (LENGTH (CDR X))))
) ) )
calcule la longueur d'une liste: (LENGTH '(A B C)) --> 3
(PUTD MEMBER
'(LAMBDA (X L)
(COND ((NULL L) NIL)
((EQUAL X (CAR L)) T)
(T (MEMBER X (CDR L)))
) ) )
cherche si une expression est prΘsente dans une liste :
(MEMBER '(A B) '(A B (A (A B)) (B A) (A B)) --> T
car on trouve (A B) en quatrieme position.
(PUTD FACT
'(LAMBDA (N)
(COND ((EQ N 0) 1)
(T (* N (FACT (- N 1))))
) ) )
calcule la factorielle d'un nombre...
- 7 -
II. PrΘsentation d'OricLisp
---------------------------
OricLisp est un dialecte LISP inspirΘ de LeLisp (JΘr⌠me Chailloux) pour
les fonctions dΘfinies par l'utilisateur (LAMBDA, FLAMBDA, MLAMBDA, cf plus bas)
et de MuLisp (Albert D.Rich, David R.Stoutemyer, Roy Feldman) pour tout le reste
mΩme si l'implΘmentation est trΦs diffΘrente (MuLisp a ΘtΘ implΘmentΘ sur les
processeurs Intel 8080 et 8086).
Un nombre relativement faible de fonctions sont prΘdΘfinies (plus de 60 tout de
mΩme !) afin de laisser le maximum d'espace disponible α l'utilisateur, mais
ses caractΘristiques puissantes permettent de dΘvelopper des applications trΦs
consΘquentes.
OricLisp a ΘtΘ Θcrit en 1986 (mais ce manuel ne l'a suivi qu'en 1995 !).
II.1 Principales caractΘristiques:
----------------------------------
* sauvegarde d'images mΘmoire permettant de reprendre une session au point
exact o∙ elle a ΘtΘ sauvΘe, ou d'enrichir le langage par des dΘfinitions
personnalisΘes, ou de produire des applications Lisp dΘmarrant automatiquement.
* arithmΘtique entiΦre sur 32 bits, exprimΘe dans une base arbitraire (de la
base 2 α la base 36)
* symboles de taille arbitraire (jusqu'α 256 caractΦres) et pouvant comporter
n'importe quel caractΦre ascii (mΩme l'espace). Les symboles peuvent ainsi
jouer le r⌠le de chaεnes de caractΦres.
* pile virtuelle segmentΘe permettant au processeur d'utiliser une pile de prΦs
de 3 Ko avec la rapiditΘ de la pile habituelle de 256 octets.
* espace utilisateur important avec en standard 16 Ko pour les paires pointΘes
et les chaεnes de caractΦres (soit plus de 4000 paires pointΘes), et prΦs de
12 Ko pour les atomes. Cette rΘpartition peut Ωtre changΘe.
* ramasse-miettes transparent pour l'utilisateur, compactant tous les espaces
utilisateurs (chaεnes, paires, nombres, symboles).
* fonctions Θvaluant ou n'Θvaluant pas leurs arguments, plus macro-fonctions
et macro-caractΦres !
* espace clos de pointeurs et donnΘes typΘes par leur adresse pour une plus
grande efficacitΘ.
II.2 Premier contact :
----------------------
Taper CLOAD"ORICLISP" depuis le Basic 1.1 de l'Oric pour charger et
exΘcuter le systΦme (ou LOAD"ORICLISP" pour un systΦme α disquettes, mais
attention, les images mΘmoires ne sont sauvΘes que sur cassette). La ligne
supΘrieure de l'Oric affiche le copyright, et le prompt "point d'interrogation"
signale que le systΦme attend des caractΦres tapΘs au clavier. Entrer des
expressions montre que l'interprΦte est actif:
Exemple:
? 'HELLO
=HELLO
? (+ 2 2)
=4
- 8 -
a) L'Θcran et le clavier
------------------------
Pour plus de rapiditΘ, les routines d'Θcrans ont ΘtΘ redΘfinies, elles
permettent de travailler avec 38 ou 40 colonnes et affichent sur la totalitΘ des
28 lignes en mode rouleau avec une routine de dΘfilement rapide.
La scrutation du clavier s'adapte au travail de l'utilisateur: lorsqu'il
est en phase d'entrΘe de caractΦres, elle est effectuΘe tous les 3/100e de
seconde, alors qu'elle n'est effectuΘe qu'une fois par seconde lorsque le
systΦme calcule, et une fois tous les 1/10e de seconde lorsque le systΦme
affiche. Ceci permet d'allier performance et confort d'utilisation, un programme
peut toujours Ωtre stoppΘ en cours de calcul par un Ctrl-C (appuyΘ 1 seconde)
et gagne ainsi environ 15% en vitesse d'exΘcution.
b) L'Θdition de lignes
----------------------
Elle est trΦs diffΘrente de celle du Basic et gΦre un buffer de 128
caractΦres (plus de 3 lignes Θcran). La derniΦre entrΘe (terminΘe par la touche
RETURN) est conservΘe dans le buffer pour permettre une correction aisΘe de
celle-ci.
Deux modes sont gΘrΘs: insertion et suppression, par dΘfaut le mode
suppression est actif et Ctrl-I permet de basculer du mode suppression au mode
insertion et vice-versa.
En mode suppression, tout caractΦre ASCII tapΘ vient remplacer le
caractΦre qui occupe la mΩme position dans le buffer (donc remplace un caractΦre
de l'entrΘe prΘcΘdente).
En mode insertion, tout caractΦre ASCII tapΘ vient s'insΘrer α la
position courante du buffer, dΘcalant ainsi vers la droite le reste du buffer.
Le dernier caractΦre ASCII (127) a une signification spΘciale puisqu'il
s'agit de DEL qui dΘtruit le dernier caractΦre tapΘ (et revient donc une
position plus α gauche dans le buffer). Deux caractΦres de contr⌠le sont dΘfinis
en plus de la bascule insertion/suppression pour utiliser l'entrΘe prΘcedente
conservΘe dans le buffer :
- Ctrl-A rΘcupΦre en effet le caractΦre courant de l'entrΘe prΘcΘdente,
laisser un Ctrl-A appuyΘ permet de recopier une entrΘe jusqu'au dernier
caractΦre dΘsirΘ.
- Ctrl-D dΘtruit le prochain caractΦre du buffer. Le rΘsultat de cette
action est malheureusement invisible pour l'utilisateur (tant qu'il n'entre pas
un Ctrl-A).
Exemple: supposons que l'utilisateur ait tapΘ la ligne suivante:
? (PUTD FACT '(LAMBDA(N) COND ((EQ N 0) 1)) (T (* N (FACT (- N 1))))))
=(LAMBDA(N) COND ((EQ N 0) 1))
Il s'aperτoit alors sans peine qu'il a oubliΘ une parenthΦse ouvrante avant le
COND et tapΘ une parenthΦse de trop aprΦs la premiΦre clause. Pour corriger son
entrΘe, il suffit qu'il appuie sur Ctrl-A jusqu'α ce que
? (PUTD FACT '(LAMBDA(N)
soit affichΘ, puis passer en mode insertion avec Ctrl-I, taper une parenthΦse
ouvrante, de nouveau enfoncer Ctrl-A jusqu'α ce que
? (PUTD FACT '(LAMBDA(N) (COND ((EQ N 0) 1)
soit affichΘ, puis dΘtruire la parenthΦse fermante superflue de l'entrΘe
prΘcΘdente par Ctrl-D, et terminer en laissant Ctrl-A enfoncΘ...
- 9 -
II.3 Les primitives OricLisp
----------------------------
a) QUOTE
--------
la fonction QUOTE classique, protΦge son argument de l'Θvaluation.
? (QUOTE (+ 2 2))
=(+ 2 2)
Remarque : le macro-caractΦre ' a le mΩme effet
? '(+ 2 2)
=(+ 2 2)
b) Les sΘlecteurs
-----------------
* les sΘlecteurs CAR et CDR sont prΘsents, ainsi que les compositions de 2 ou 3
CAR ou CDR (par exemple, "(CADR X)" est l'Θquivalent de "(CAR (CDR X))" ).
OricLisp possΦde un espace clos de pointeurs, on peut toujours appliquer un CAR
ou un CDR sans erreur; dans le cas d'un atome, CAR renvoie la valeur associΘe α
l'atome et CDR la liste de propriΘtΘs associΘe α l'atome. La valeur d'un symbole
est par dΘfaut lui-mΩme (tant qu'aucune valeur ne lui a ΘtΘ liΘe), celle d'un
nombre est toujours le mΩme nombre. La liste de propriΘtΘs d'un symbole est vide
par dΘfaut (elle vaut NIL), celle d'un nombre reflΦte son signe : T pour un
nombre positif ou nul, NIL pour un nombre nΘgatif.
Exemple:
? (CAR '(A.B))
=A
? (CADR '(A B C))
=B
? (CDDR '(A B C D))
=(C D)
* LAST renvoie la derniΦre paire pointΘe de premier niveau de son argument (et
non le dernier ΘlΘment) ou NIL si l'argument est un atome. Renvoyer la derniΦre
paire pointΘe plut⌠t que le dernier ΘlΘment permet de modifier cette paire
pointΘe (pour ajouter en fin de liste avec RPLACD par exemple, mais attention
aux effets de ces modifications physiques), et le dernier ΘlΘment peut Ωtre
facilement obtenu avec un CAR supplΘmentaire.
Exemple:
? (LAST '(A B C D))
=(D)
? (LAST '(A B.C))
=(B.C)
? (CAR (LAST '(A B C D)))
=D
* ASSOC cherche le premier argument (la clef) dans la A-liste fournie par le
deuxiΦme argument. Une A-liste (liste associative) est une liste de la forme
((clef1.val1) (clef2.val2) ... (clefN.valN))
ASSOC compare (avec EQUAL) le premier argument avec chacune des clefs (les
ΘlΘments atomiques de la A-liste sont sautΘs) et renvoie l'ΘlΘment complet de
la premiΦre comparaison avec succΦs, sinon NIL. Certains Lisp ne renvoient que
la partie valeur de la paire pointΘe, il suffit de rajouter un CDR pour obtenir
le mΩme rΘsultat (tandis que renvoyer l'ensemble permet de modifier la valeur
associΘe α la clef).
- 10 -
Exemple:
? (ASSOC 'MARTIN '((DUPONT JEAN 61586273) (MARTIN JACQUES 61483922)
(DUPONT ALAIN 61289019)))
=(MARTIN JACQUES 61483922)
* MEMBER cherche (avec EQUAL) le premier argument dans la liste fournie par le
second argument et renvoie la fin de liste commenτant α l'ΘlΘment trouvΘ, ou
NIL s'il n'est pas trouvΘ. Certains Lisp ne renvoient qu'une valeur boolΘenne T
ou NIL, ici le rΘsultat peut servir (pour dΘtruire l'ΘlΘment de la liste, par
exemple).
Exemple:
? (MEMBER '(MARTIN JACQUES 61483922)
'((DUPONT JEAN 61586273) (MARTIN JACQUES 61483922)
(DUPONT ALAIN 61289019)))
=((MARTIN JACQUES 61483922) (DUPONT ALAIN 61289019)))
c) Les constructeurs
--------------------
* CONS construit une paire pointΘe avec les deux arguments fournis.
Exemple:
? (CONS 'A 'B)
=(A.B)
? (CONS 'A '(B C))
=(A B C)
* LIST construit une liste avec tous les arguments fournis.
? (LIST 'a '(b c) 'd)
=(a (b c) d)
* OBLIST construit une liste de tous les symboles
? (OBLIST)
=(LAST OBLIST DIV MOD ................... LAMBDA T NIL)
* APPEND construit une liste qui est la concatΘnation des deux listes passΘes
en argument. De nouvelles paires pointΘes sont crΘes pour le dΘbut de cette
liste, contrairement α NCONC. Remarque : APPEND avec un seul argument peut Ωtre
utilisΘ pour copier une liste.
? (APPEND '(A (B C) D) '(E F (G H)))
=(A (B C) D E F (G H))
* REVERSE construit une nouvelle liste qui est une liste renversΘe de son
premier argument (l'implΘmentation utilise un deuxiΦme paramΦtre pour cumuler
le rΘsultat partiel. De ce fait, si un deuxiΦme argument est fourni, il sera
concatΘnΘ α la liste renversΘe)
? (REVERSE '(A (B C) D))
=(D (B C) A)
? (REVERSE '(A (B C) D) '(E F))
=(D (B C) A E F)
* GC n'est pas un constructeur comme CONS, LIST, OBLIST, APPEND ou REVERSE. Mais
il intervient lui aussi dans la gestion de l'espace mΘmoire. Tous les construc-
-teurs font appel α CONS pour allouer une paire pointΘe. Lorsque plus aucune
paire ne peut Ωtre allouΘe, le ramasse-miettes (Garbage Collector) est invoquΘ
automatiquement. Il peut aussi Ωtre appelΘ explicitement avec la fonction GC.
- 11 -
d) Les prΘdicats
----------------
* EQUAL renvoie T si ses deux arguments sont Θgaux. Toutes les branches sont
comparΘes.
? (EQUAL '(A (B C) D) '(A (B C) D))
=T
* EQ renvoie T si ses deux arguments ne sont qu'un mΩme objet en mΘmoire. Il ne
peut exister deux symboles de mΩme nom. Il peut exister plusieurs nombres de
mΩme valeur mais EQ considΦre que ce sont les mΩmes. Lorsque l'on partage des
paires pointΘes, EQ est un test intΘressant, parce qu'il est trΦs rapide (il ne
regarde pas les branches)
? (EQ '(A (B C) D) '(A (B C) D))
=NIL
* ATOM renvoie T si son argument est un atome (donc pas une paire pointΘe)
? (ATOM 'A)
=T
? (ATOM '(A B))
=NIL
? (ATOM '())
=T
* NULL renvoie T si son argument est NIL (la liste vide)
? (NULL 'A)
=NIL
? (NULL '(A B))
=NIL
? (NULL '())
=T
* PLUSP renvoie T si son argument est un nombre positif ou nul.
? (PLUSP 0)
=T
? (PLUSP 'A)
=NIL
* MINUSP renvoie T si son argument est un nombre nΘgatif
? (MINUSP -3)
=T
? (MINUSP '(A B))
=NIL
* ZEROP renvoie T si son argument est le nombre 0
? (ZEROP NIL)
=NIL
? (ZEROP 0)
=T
Remarque : n'importe quelle expression peut servir de prΘdicat puisque toute
valeur non NIL est considΘrΘe comme "Vrai". Par exemple, T est le prΘdicat
toujours vrai, utile pour la derniΦre clause d'une conditionnelle.
- 12 -
e) La conditionnelle et les structures de contr⌠le
--------------------------------------------------
* COND est la conditionnelle classique de la forme suivante :
(COND (pred1 exp11 exp12 ... exp1N)
(pred2 exp21 exp22 ... exp2M)
....
)
Chaque prΘdicat pred1, pred2 ... est ΘvaluΘ sΘquentiellement jusqu'α trouver
une valeur non NIL pour un prΘdicat predI, alors les expressions associΘes
expI1, expI2... sont ΘvaluΘes en sΘquence, la derniΦre donnant la valeur de
retour du COND (si aucune expression n'est associΘe au prΘdicat, c'est la valeur
du prΘdicat qui est renvoyΘe). Avoir plusieurs expressions au lieu d'une α la
suite du prΘdicat n'est utile que si les expressions ont des effets de bord.
Exemple:
? (COND ((< A B) A) (B)) renvoie le minimum des nombres A et B
* PROGN Θvalue sΘquentiellement tous ses arguments et renvoie la valeur du
dernier. PROGN n'a d'intΘrΩt que si les expressions font des effets de bord et
peut Ωtre gΘnΘralement omis du fait du PROGN implicite dans les dΘfinitions de
fonction et dans la conditionnelle COND.
Exemple:
? (PROGN (PRIN '(FACT 5)) (FACT 5))
(FACT 5)=120
* PROG1 Θvalue sΘquentiellement tous ses arguments et renvoie la valeur du
premier. Son usage est de mΩme liΘ aux effets de bord.
Exemple:
? (SETQ A (PROG1 B (SETQ B A))) Θchange les valeurs associΘes α A et B
* AND est α la fois le "et" logique classique et une structure de contr⌠le
(Θquivalente α des "si...alors..." imbriquΘs). Les arguments sont ΘvaluΘs en
sΘquence jusqu'α ce qu'une valeur NIL soit trouvΘe, auquel cas le rΘsultat est
NIL et les arguments suivants sont "court-circuitΘs". Si tous les arguments
s'Θvaluent α une valeur non NIL, la valeur du dernier est renvoyΘe, le rΘsultat
est donc "Vrai".
Exemple:
? (AND (ZEROP 1) (PRINT 'ARGH))
=NIL et on remarque que le deuxiΦme argument n'est pas ΘvaluΘ
? (AND A B C) est Θquivalent α (COND (A (COND (B (COND (C))))))
* OR est α la fois le "ou" logique classique et une structure de contr⌠le (une
suite de "si...alors...sinon si"). Les arguments sont ΘvaluΘs en sΘquence
jusqu'α ce qu'une valeur non NIL soit trouvΘe (cette valeur est alors retournΘe
et l'Θvaluation des arguments restants est court-circuitΘe). Si tous les
arguments sont NIL, NIL est renvoyΘ.
Exemple:
? (OR NIL '() 'A (PRINT 'ARGH))
=A
? (OR A B C) est Θquivalent α (COND (A) (B) (C))
* NOT est le "non" logique (ce n'est pas une structure de contr⌠le mais il
permet d'inverser la signification de prΘdicats) et renvoie T si son argument
est NIL, sinon NIL. NOT est donc identique au prΘdicat NULL.
Exemple:
? (NOT NIL)
=T
- 13 -
* WHILE est une structure de contr⌠le de style impΘratif de la forme
(WHILE pred exp1 exp2 ... expN)
Tant que pred s'Θvalue α "Vrai" (non NIL), WHILE itΦre sur l'Θvaluation de exp1,
exp2 ... expN (il faut donc nΘcessairement un effet de bord pour que la valeur
de pred devienne NIL). WHILE est implΘmentΘ sans rΘcursivitΘ et permet donc des
boucles importantes (ou infinies) sans saturation de la pile.
Exemple:
? (WHILE T (PRINT (EVAL (READ)))
une boucle infinie pour un nouvel interprΦte !
? (WHILE (NOT (ZEROP N)) (SETQ N (- N 1)))
=NIL
dΘcrΘmente N jusqu'α 0
f) L'arithmΘtique
-----------------
* RADIX change la base pour la reprΘsentation des nombres entrΘs au clavier et
affichΘs α l'Θcran. La nouvelle base est donnΘe en argument et doit Ωtre
comprise entre 2 et 36 (au delα de la base 10, jusqu'α 26 lettres peuvent Ωtre
employΘes). RADIX renvoie la nouvelle base en rΘsultat (mais on peut remarquer
que l'affichage d'une base est toujours "10"). Si un argument non numΘrique est
fourni, la base n'est pas changΘe et est renvoyΘe en rΘsultat.
Exemple:
? (RADIX 8)
=10
? (+ 6 7)
=15
? (RADIX 12)
=10
? (+ 6 7)
=13
* les 4 opΘrations entiΦres sont +,-,*,/. Elles prennent deux nombres en
argument et renvoient le rΘsultat. Une erreur NONNUMERIC est levΘe si un
opΘrande n'est pas un nombre et l'erreur DIVBYZERO est levΘe en cas de division
par 0. / renvoie le quotient de la division entiΦre tandis que MOD renvoie le
reste. Souvent, quotient et reste sont tous deux utiles, DIV permet de ne faire
qu'une fois la division et renvoie un paire pointΘe (quotient.reste)
Exemple:
? (DIV 10 3)
=(3.1)
* les prΘdicats d'ordre sont < et >. Ils renvoient T si l'ordre est vΘrifiΘ
entre les deux arguments et NIL sinon. Les comparaisons au sens large (ΘgalitΘ
incluse) ne sont pas des primitives, il faut inverser le test.
Exemple:
? (NOT (< A B))
renvoie T si A>=B, et NIL sinon.
- 14 -
g) Les modifications physiques et les fonctions α effet de bord
---------------------------------------------------------------
* SETQ lie la valeur fournie par le second argument au symbole spΘcifiΘ par
le premier (le premier argument n'est pas ΘvaluΘ). Si le symbole Θtait local
α une fonction, la liaison est perdue α la sortie de la fonction.
Exemple:
? (SETQ A '(1 2))
=(1 2)
? A
=(1 2)
? (SETQ A 'B)
=B
? A
=B
* SET lie la valeur fournie par le second argument au symbole spΘcifiΘ par le
premier (les deux arguments sont ΘvaluΘs).
Exemple:
? (SETQ A 'B)
=B
? (SET A 3)
=3
? B
=3
? A
=B
* RPLACA et RPLACD remplacent respectivement le CAR et le CDR de la paire
pointΘe donnΘe en premier argument par le deuxiΦme argument, et renvoient la
paire modifiΘe.
Exemple:
? (SETQ A '(1 2))
=(1 2)
? (RPLACA A 0)
=(0 2)
? A
(0 2)
? (RPLACD A '(3 4))
=(0 3 4)
? A
(0 3 4)
* NCONC est la concatΘnation de liste, comme APPEND mais sans consommation de
paire pointΘe. NCONC modifie la fin de la liste donnΘe par son premier argument
pour lui faire suivre la deuxiΦme liste.
Exemple:
? (SETQ A '(1 2))
=(1 2)
? (NCONC A '(3 4 5))
=(1 2 3 4 5)
? A
=(1 2 3 4 5)
- 15 -
* MEMORY permet de lire ou d'Θcrire un octet en mΘmoire. Si un seul argument est
donnΘ, c'est une adresse dont MEMORY renverra le contenu. Si deux arguments
numΘriques sont donnΘs, MEMORY change l'adresse mΘmoire avec l'octet donnΘ en
second argument, et renvoie l'ancienne valeur.
Exemple:
? (RADIX 16)
=10
? (MEMORY 26B 1)
=7 change la couleur de papier...
* TIME renvoie la valeur d'une horloge en 1/100e de secondes et peut donc servir
α mesurer le temps d'exΘcution d'un programme.
Exemple:
? (SETQ T1 (TIME)) (GC) (- (TIME) T1)
=5
* PRINT affiche l'expression donnΘe en paramΦtre, et retourne cette expression
en rΘsultat. PRIN fait la mΩme chose mais sans terminer par un retour charriot.
Exemple:
? (PRINT 'HELLO)
HELLO
=HELLO
? (PRINT '(A (B C) D))
(A (B C) D)
=(A (B C) D)
? (PRIN 0)
0=0
* READ lit une expression au clavier et la renvoie en rΘsultat. Si plus d'une
expression est donnΘe, les autres restent dans le buffer clavier α la disposi-
-tion d'un READ ultΘrieur.
Exemple:
? (PRIN '(+ 2 2)) (+ 2 2)
(+ 2 2)=(+ 2 2)
=4
? (SETQ A (READ)) (+ 2 2)
=(+ 2 2)
On remarque que (+ 2 2) n'a pas ΘtΘ lu par l'interprΦte mais
bien par le READ.
* SAVE produit sur cassette une image mΘmoire du nom de l'argument donnΘ en
paramΦtre. Il n'y a pas de fonction LOAD, une image mΘmoire contient le noyau
OricLisp et dΘmarre automatiquement au point exact du SAVE en tapant la commande
CLOAD du Basic de l'Oric.
Exemple:
? (PROGN (SAVE 'IMAGE1) 'HELLO)
=HELLO et un fichier IMAGE1 est Θcrit sur cassette que l'on lancera
depuis le Basic :
CLOAD"IMAGE1"
=HELLO
?
- 16 -
h) La dΘfinition de fonction
----------------------------
* Une fonction OricLisp Θvaluant ses arguments est une expression de la forme
(LAMBDA (param1 ... paramN) exp1 exp2 ... expM)
o∙ param1 ... paramN sont les noms des paramΦtres formels et exp1, exp2 ... expM
le corps de la fonction (l'Θvaluation d'une LAMBDA comporte donc un PROGN
implicite).
Exemple:
(LAMBDA () (/ (TIME) 100))
une fonction sans paramΦtres qui renvoie l'horloge en secondes
(LAMBDA (N) (+ N 1))
une fonction qui renvoie le nombre successeur de son argument
Attention ! le deuxiΦme ΘlΘment de la liste de la fonction (les paramΦtres
formels) doit impΘrativement Ωtre une liste (Θventuellement vide).
Les LAMBDA sont des fonctions "sans nom" qui peuvent Ωtre utilisΘes de la mΩme
faτon que les primitives du langage.
Exemple:
? ( (LAMBDA(N)(+ N 1)) 3)
=4
Pour associer un nom α une LAMBDA, on utilise la primitive PUTD.
? (PUTD 'INCR '(LAMBDA(N)(+ N 1)))
=(LAMBDA (N) (+ N 1))
? (INCR 3)
=4
Remarque : un symbole peut simultanΘment avoir une valeur, une liste de
propriΘtΘs et une dΘfinition de fonction associΘes.
Exemple:
? (SETQ INCR '(1 2))
=(1 2)
? (INCR 3)
=4
? INCR
=(1 2)
* GETD permet de lire la dΘfinition de fonction d'un symbole. GETD renvoie la
LAMBDA d'une fonction dΘfinie en LISP, T pour une fonction en langage machine
et NIL si la fonction n'est pas dΘfinie.
Exemple:
? (GETD 'T)
=NIL
? (GETD 'EVAL)
=T
? (GETD 'INCR)
=(LAMBDA (N) (+ N 1))
* MOVD permet de copier la dΘfinition de fonction d'un symbole dans un autre
symbole, dΘfinissant un synonyme pour une fonction sans consommer d'espace
mΘmoire supplΘmentaire (en dehors du symbole).
Exemple:
? (MOVD 'APPEND 'COPY)
=T
? (COPY '(A B (C D) E))
=(A B (C D) E)
- 17 -
II.4 Concepts avancΘs
---------------------
a) Fonctions sans Θvaluation
----------------------------
OricLisp permet de dΘfinir des fonctions n'Θvaluant pas leurs
arguments, une telle fonction est de la forme
(FLAMBDA param exp1 exp2 ... expM)
Un seul symbole est fourni en paramΦtre formel, il sera liΘ α la liste de tous
les paramΦtres non-ΘvaluΘs.
Les fonctions n'Θvaluant pas leurs arguments permettent de dΘfinir de
nouvelles structures de contr⌠le (elles sont parfois aussi utilisΘes
pour dΘfinir des fonctions dont le nombre de paramΦtres est indΘterminΘ)
Exemple:
(FLAMBDA L
(COND ((EVAL (CAR L)) (EVAL (CADR L)))
(T (EVAL (CADDR L)))
) ) dΘfinit un IF X THEN Y ELSE Z !!
que l'on liera bien s√r de la faτon suivante:
? (PUTD 'IF '(FLAMBDA L
? (COND ((EVAL (CAR L)) (EVAL (CADR L)))
? (T (EVAL (CADDR L))) )))
=(FLAMBDA L (COND ((EVAL (CAR L))(EVAL (CADR L))) (T (EVAL (CADDR L)))))
? (IF NIL (PRINT 'ARGH) (PRINT 'OK))
OK
=OK et on constate qu'effectivement, le "alors" n'est pas ΘvaluΘ,
contrairement α ce qui serait arrivΘ si on avait dΘfini le IF
par (LAMBDA (X Y Z) (COND (X Y) (Z)))
Exemple:
(PUTD 'PLUS '(FLAMBDA L
(COND ((NULL L) 0)
((+ (EVAL (CAR L)) (PLUS (CDR L))))
)))
dΘfinit une opΘration PLUS qui somme tous ses arguments
? (PLUS 1 2 3 4 5 6 7)
=28
- 18 -
b) Macro-caractΦres
-------------------
Les macro-caractΦres sont des caractΦres qui ont une fonction associΘe
appelΘe lorsqu'ils sont rencontrΘs durant une lecture clavier. Le caractΦre
apostrophe (quote) est le seul macro-caractΦre dΘfini par dΘfaut. READ consulte
une table (α l'adresse $8000) pour savoir si un caractΦre est un macro-
caractΦre. Pour dΘfinir un macro-caractΦre, il suffit de positionner un octet
dans cette table et d'associer une fonction au caractΦre choisi.
Exemple:
? (PUTD '"#" '(LAMBDA () (EVAL (READ))))
=(LAMBDA NIL (EVAL (READ)))
? (RADIX 16) (MEMORY (+ 8000 3) FF)
=10
=0 # a pour code ASCII 23h, la table commence au caractΦre 20h...
? (SETQ N 8) (PUTD 'TEST '(LAMBDA (X) (/ X #(* N N))))
=8
=(LAMBDA (X) (/ X 64))
et on constate que l'expression prΘcΘdΘe de # a ΘtΘ ΘvaluΘe...
? (QUOTE # (+ 2 2))
=4 ... mΩme α l'intΘrieur d'une expression protΘgΘe
c) Macro-fonctions
------------------
Les macro-fonctions sont des fonctions α double Θvaluation dΘfinies
ainsi :
(MLAMBDA param exp1 exp2 ... expN)
Un seul symbole est fourni en paramΦtre formel; α l'exΘcution il est liΘ α
l'ensemble de la liste invoquant la macro (sans Θvaluation), y compris la
fonction qui apparaεt en tΩte de liste. Le corps de la macro est exΘcutΘ (les
expressions exp1 ... expN) et le rΘsultat est de nouveau fourni α l'Θvaluation !
Le but d'une macro est donc le suivant : remplacer l'appel α la macro par une
autre expression.
Exemple:
(PUTD 'IF '(MLAMBDA L
(LIST 'COND
(LIST (CADR L) (CADDR L))
(LIST (CAR (CDDDR L)))
)))
dΘfinit une macro "IF" qui va construire une expression COND
contenant les arguments du IF.
Ainsi,
(IF (PLUSP N) N (- 0 N))
va construire la liste
(COND ((PLUSP N) N) ((- 0 N)))
puis celle-ci sera ΘvaluΘe, donnant la valeur absolue de N.
- 19 -
Ce mΘcanisme puissant qui peut paraεtre bien inefficace pour dΘfinir une
fonction IF (appel de la macro, parcours des arguments et allocation de paires
pointΘes pour construire une expression COND, avant d'enfin Θvaluer cette
derniΦre), surtout lorsque l'on compare α la version du IF avec FLAMBDA, rΘvΦle
tout son intΘrΩt dans le cadre des macro-fonctions "Θcrasantes". Modifions
lΘgΦrement la dΘfinition du IF ainsi :
(PUTD 'IF '(MLAMBDA L
(RPLACA L 'COND)
(RPLACD L (LIST (LIST (CADR L) (CADDR L))
(LIST (CAR (CDDDR L)))))
))
et maintenant le IF va se remplacer une fois pour toute par un
COND α la premiΦre exΘcution.
Exemple:
? (PUTD 'FACT '(LAMBDA (N)
? (IF (ZEROP N) 1 (* N (FACT (- N 1))))))
=(LAMBDA (N) (IF (ZEROP N) 1 (* N (FACT (- N 1)))))
? (FACT 3)
=6
? (GETD 'FACT)
=(LAMBDA (N) (COND ((ZEROP N) 1) ((* N (FACT (- N 1))))))
α la premiΦre exΘcution du IF, le code de FACT s'est modifiΘ ! Le code est
maintenant plus efficace que si on utilisait un IF implΘmentΘ avec une FLAMBDA !
Ce genre de manipulation est intΘressant dans le cadre de portage de programmes
LISP d'une machine α une autre (par exemple, ici, lorsque le IF n'existe pas)
ou pour dΘfinir efficacement des fonctions. Les macros ne sont jamais trΦs
lisibles, et plus dΘlicates α Θcrire que des LAMBDA normales, mais utiliser des
macros rend souvent un programme plus lisible sans perdre en efficacitΘ.
Exemple:
la construction LET existe dans de nombreux LISP pour dΘfinir des
symboles locaux α un bloc. Ainsi, le IF prΘcΘdent s'Θcrirait plus lisiblement :
(PUTD 'IF '(MLAMBDA L
(LET (
(X (CADR L))
(Y (CADDR L))
(Z (CAR (CDDDR L)))
)
(RPLACA L 'COND)
(RPLACD L (LIST (LIST X Y) (LIST Z)))
)))
le LET peut Ωtre dΘfini efficacement par une macro le remplaτant en LAMBDA :
(PUTD 'LET '(MLAMBDA L
(RPLACA L (CONS 'LAMBDA
(CONS (ALLCAR (CADR L))
(CDDR L))))
(RPLACD L (ALLCADR (CADR L)))
))
o∙ ALLCAR et ALLCADR permettent de rΘcupΘrer respectivement les paramΦtres
formels et les arguments effectifs :
(PUTD 'ALLCAR '(LAMBDA (L)
(COND ((NULL L) NIL)
((CONS (CAAR L) (ALLCAR (CDR L)))))))
(PUTD 'ALLCADR '(LAMBDA (L)
(COND ((NULL L) NIL)
((CONS (CADAR L) (ALLCADR (CDR L)))))))
- 20 -
d) Fonctions d'ordre supΘrieur
------------------------------
OricLisp permet de passer une fonction en paramΦtre d'une autre fonction.
Toutefois, le premier ΘlΘment d'une liste (la fonction) n'est jamais ΘvaluΘ par
la fonction EVAL, on ne peut donc invoquer directement une fonction obtenue en
argument. Par exemple, la fonctionnelle classique MAP (qui applique une fonction
α tous les ΘlΘments d'une liste et renvoie la liste des rΘsultats) ne peut
s'Θcrire ainsi avec OricLisp :
(PUTD 'MAP '(LAMBDA (F L)
(COND ((NULL L) NIL)
((CONS (F (CAR L))
(MAP F (CDR L)))))))
parce que le symbole F en position de fonction ne sera pas ΘvaluΘ (Remarque :
dans beaucoup de dialectes LISP, cette dΘfinition de MAP ne marche que si F n'a
pas de dΘfinition associΘe).
De plus, OricLisp n'a pas de fonction APPLY mais les constructions de type
(APPLY F ARGS) peuvent Ωtre facilement remplacΘes par (EVAL (CONS F ARGS)).
Ainsi, le MAP peut s'Θcrire :
(PUTD 'MAP '(LAMBDA (F L)
(COND ((NULL L) NIL)
((CONS (APPLY F (LIST (CAR L)))
(MAP F (CDR L)))))))
et cette dΘfinition marche sans restriction. APPLY peut Ωtre dΘfini par une
simple (LAMBDA (F L) (EVAL (CONS F L)))
ou par une macro :
(MLAMBDA L
(RPLACA L 'EVAL)
(RPLACD L (LIST (LIST 'CONS (EVAL (CADR L)) (EVAL (CADDR L)))))).
Exemple:
? (MAP '(LAMBDA(X)(* X X)) '(1 2 3 4 5))
=(1 4 9 16 25)
? (MAP 'PLUSP '(1 8 -2 6 -3))
=(T T NIL T NIL)
A. Annexe A: Copyright
----------------------
Le programme OricLisp et le prΘsent manuel sont Copyright Fabrice FrancΦs.
Ils sont distribuΘs gratuitement et vous ne devez avoir payΘ que le prix
du support pour les avoir: il y a de meilleurs moyens de dΘpenser votre
argent, par exemple en me l'envoyant ! J'accepte les donations envoyΘes
α l'adresse suivante :
Fabrice FrancΦs
16, allΘe du Vaucluse
31770 COLOMIERS
FRANCE
- 21 -
B. Annexe B: DΘfinitions formelles
----------------------------------
Pour le "LISPien", une fonction LISP vaut mieux qu'un long discours,
comment mieux dΘcrire le comportement d'une primitive sinon avec son code Θcrit
en LISP ? Bon nombre des fonctions implΘmentΘes en langage machine dans OricLisp
peuvent Ωtre Θcrites en LISP, mais elle seraient alors moins efficaces et
souvent trΦs consommatrices d'espace de pile (α cause des rΘcursivitΘs).
Le noyau vraiment minimal est composΘ des dΘfinitions LAMBDA, FLAMBDA,
MLAMBDA et des primitives READ, EVAL, PRIN, COND, CAR, CDR, CONS, PUTD, GETD,
NULL, ATOM, RPLACA, RPLACD, EQ, TIME, MEMORY, RADIX, <, >, +, -, *, /, MOD.
Les autres peuvent s'Θcrire en fonction de ces primitives de base. Les dΘfini-
-tions qui suivent ont exactement le mΩme comportement que les routines en
langage machine (elles renvoient les mΩmes rΘsultats en toutes circonstances),
mΩme si l'implΘmentation effective est diffΘrente pour des raisons de
performance. Les seules primitives implΘmentΘes de faτon rΘcursive sont LIST et
APPEND (elles ne le devraient pas...). Enfin, APPLY n'est pas un symbole dΘfini
dans OricLisp, mais il existe en interne; il peut Ωtre dΘfini par
(PUTD 'APPLY '(LAMBDA (F ARGS) (EVAL (CONS F ARGS))))
mΩme si l'implΘmentation rΘelle ne consomme pas de paire pointΘe.
(PUTD 'AND
'(FLAMBDA L
(COND ((ATOM L) T)
((ATOM (CDR L)) (EVAL (CAR L)))
((EVAL (CAR L)) (APPLY 'AND (CDR L))))))
(PUTD 'APPEND
'(LAMBDA (X Y)
(COND ((ATOM X) Y)
((CONS (CAR X) (APPEND (CDR X) Y))))))
(PUTD 'ASSOC '(LAMBDA (X L)
(COND ((ATOM L) NIL)
((ATOM (CAR L)) (ASSOC X (CDR L))
((EQUAL X (CAAR L)) (CAR L))
((ASSOC X (CDR L))))))
(PUTD 'CAAAR '(LAMBDA (X) (CAAR (CAR X))))
(PUTD 'CAADR '(LAMBDA (X) (CAAR (CDR X))))
(PUTD 'CADAR '(LAMBDA (X) (CADR (CAR X))))
(PUTD 'CADDR '(LAMBDA (X) (CADR (CDR X))))
(PUTD 'CDAAR '(LAMBDA (X) (CDAR (CAR X))))
(PUTD 'CDADR '(LAMBDA (X) (CDAR (CDR X))))
(PUTD 'CDDAR '(LAMBDA (X) (CDDR (CAR X))))
(PUTD 'CDDDR '(LAMBDA (X) (CDDR (CDR X))))
(PUTD 'CAAR '(LAMBDA (X) (CAR (CAR X))))
(PUTD 'CADR '(LAMBDA (X) (CAR (CDR X))))
(PUTD 'CDAR '(LAMBDA (X) (CDR (CAR X))))
(PUTD 'CDDR '(LAMBDA (X) (CDR (CDR X))))
(PUTD 'DIV '(LAMBDA (X Y) (CONS (/ X Y) (MOD X Y))))
(PUTD 'EQUAL
'(LAMBDA (X Y)
(COND ((ATOM X) (EQ X Y))
((ATOM Y) NIL)
((EQUAL (CAR X) (CAR Y)) (EQUAL (CDR X) (CDR Y))))))
- 22 -
(PUTD 'LAST '(LAMBDA (L)
(COND ((ATOM L) NIL)
((ATOM (CDR L)) L)
((LAST (CDR L))))))
(PUTD 'LENGTH '(LAMBDA (L)
(COND ((ATOM L) 0)
((+ 1 (LENGTH (CDR L)))))))
(PUTD 'LIST
'(FLAMBDA L
(COND ((ATOM L) NIL)
((CONS (EVAL (CAR L)) (APPLY 'LIST (CDR L)))))))
(PUTD 'MEMBER
'(LAMBDA (X L)
(COND ((ATOM L) NIL)
((EQUAL X (CAR L)) L)
((MEMBER X (CDR L))))))
(PUTD 'NCONC '(LAMBDA (X Y)
(COND ((ATOM X) Y)
(T (RPLACD (LAST X) Y) X))))
(PUTD 'OR '(FLAMBDA L
(COND ((ATOM L) NIL)
((EVAL (CAR L)))
((APPLY 'OR (CDR L))))))
(PUTD 'PROG1
'(FLAMBDA L
(COND ((ATOM L) NIL)
(T ((LAMBDA (X) (APPLY 'PROGN (CDR L)) X) (EVAL (CAR L)))))))
(PUTD 'PROGN
'(FLAMBDA L
(COND ((ATOM L) NIL)
((ATOM (CDR L)) (EVAL (CAR L)))
(T (EVAL (CAR L)) (APPLY 'PROGN (CDR L))))))
(PUTD 'QUOTE '(FLAMBDA L (CAR L)))
(PUTD 'REVERSE
'(LAMBDA (X Y)
(COND ((ATOM X) NIL)
((ATOM (CDR X)) (CONS (CAR X) Y))
((REVERSE (CDR X) (CONS (CAR X) Y))))))
(PUTD 'SET '(LAMBDA (X Y) (RPLACA X Y) Y)))
(PUTD 'SETQ '(FLAMBDA L (COND ((NAME (CAR L)) (SET (CAR L) (EVAL (CADR L)))))))
(PUTD 'WHILE
'(FLAMBDA L
(COND ((EVAL (CAR L))
(APPLY 'PROGN (CDR L))
(APPLY 'WHILE L)))))
- 23 -
C. Annexe C: portage de programmes MuLisp
-----------------------------------------
En dehors de l'Θvaluation des fonctions utilisateurs (LAMBDA, FLAMBDA, MLAMBDA),
OricLisp possΦde des primitives trΦs proches de celles de MuLisp, il est donc
aisΘ de porter des programmes de MuLisp vers OricLisp (et vice-versa). Pour
laisser plus de place α l'utilisateur, OricLisp ne dΘfinit pas toutes les
fonctions qui existent dans MuLisp.
On pourra utiliser les dΘfinitions suivantes qui fournissent un Θquivalent
exact :
(PUTD 'NTH '(LAMBDA (N L)
(COND ((ZEROP N) (CAR L))
((ATOM L) NIL)
((NTH (- N 1) (CDR L)))))) pour une version α la MacLisp
(PUTD 'NTH '(LAMBDA (L N)
(COND ((EQ N 1) L)
((ATOM L) NIL)
((NTH (CDR L) (- N 1)))))) pour une version α la InterLisp
(PUTD 'TCONC '(LAMBDA (PTR OBJ)
(SETQ OBJ (LIST OBJ))
(COND ((ATOM PTR) (CONS OBJ OBJ))
((ATOM (CDR PTR)) (RPLACA PTR OBJ) (RPLACD PTR OBJ))
(T (RPLACD (CDR PTR) OBJ) (RPLACD PTR OBJ)))))
(PUTD 'LCONC '(LAMBDA (PTR L)
(COND ((ATOM L) PTR)
((ATOM PTR) (CONS L (LAST L)))
((ATOM (CDR PTR)) (RPLACA PTR L) (RPLACD PTR (LAST L)))
(T (RPLACD (CDR PTR) L) (RPLACD PTR (LAST L))))))
(PUTD 'EVENP '(LAMBDA (N) (COND ((NUMBERP N) (ZEROP (MOD N 2))))))
(PUTD 'POP '(FLAMBDA P
(COND ((NOT (NAME (CAR P))) NIL)
((ATOM (CAAR P)) NIL)
((PROG1 (CAAAR P) (SET (CAR P) (CDAAR P)))))))
(PUTD 'PUSH '(FLAMBDA P
(COND ((NULL (CADR P)) NIL)
((NAME (CADR P)) (SET (CADR P) (CONS (EVAL (CAR P))
(CADR P)))))))
(PUTD 'PUT '(LAMBDA (NAM KEY OBJ)
((LAMBDA(ELEM)
(COND ((NULL ELEM) (RPLACD NAM (CONS (CONS KEY OBJ) (CDR NAM))) OBJ)
(T (RPLACD ELEM OBJ) OBJ))) (ASSOC KEY (CDR NAM)))))
(PUTD 'GET '(LAMBDA (NAM KEY) (CDR (ASSOC KEY (CDR NAM)))))
- 24 -
(PUTD 'REMPROP '(LAMBDA (NAM KEY)
(COND ((ATOM (CDR NAM)) NIL)
((EQUAL (CAADR NAM) KEY)
(SETQ KEY (CDADR NAM))
(RPLACD NAM (CDDR NAM))
KEY)
((REMPROP (CDR NAM) KEY)))))
(PUTD 'FLAGP '(LAMBDA (NAM ATT) (MEMBER ATT (CDR NAM))))
(PUTD 'FLAG '(LAMBDA (NAM ATT)
(COND ((FLAGP NAM ATT) ATT)
(T (RPLACD NAM (CONS ATT (CDR NAM))) ATT))))
(PUTD 'REMFLAG '(LAMBDA (NAM ATT)
(COND ((ATOM (CDR NAM)) NIL)
((EQUAL ATT (CADR NAM)) (RPLACD NAM (CDDR NAM)) T)
((REMFLAG (CDR NAM) ATT)))))
(PUTD 'GCD '(LAMBDA (X Y)
(COND ((NOT (ZEROP Y)) (GCD Y (MOD X Y)))
((PLUSP X) X)
((- 0 X)))))
(PUTD 'COMMENT '(FLAMBDA L NIL))
Les diffΘrences suivantes peuvent apparaεtre lors d'un portage :
* le corps d'une fonction MuLisp et d'une fonction OricLisp sont ΘvaluΘs
diffΘremment. MuLisp implΘmente un COND/PROGN implicite au lieu d'un PROGN
implicite dans les corps de fonction (et les clauses de COND), ce qui permet
de rΘduire la taille du code mais introduit une ambigu∩tΘ dans le cas de
LAMBDAs qui sont alors prises pour des prΘdicats. Le COND est obligatoire dans
les corps de fonction OricLisp.
* PLUSP renvoie NIL pour 0 dans MuLisp et T dans OricLisp. De plus, le CDR
d'un nombre (la liste de propriΘtΘs) α une signification inversΘe : il vaut T
pour un nombre positif ou nul dans OricLisp, tandis qu'il vaut T pour un nombre
nΘgatif dans MuLisp.
GREATERP et LESSP acceptent plus de deux arguments dans MuLisp, de telles
expressions doivent Ωtre Θcrites avec plusieurs > et < dans OricLisp. De mΩme,
PLUS, DIFFERENCE, TIMES admettent plus de deux arguments dans MuLisp, il est
facile de rΘΘcrire ces expressions avec plusieurs +, - ou *.
* les fonctions sur les chaεnes de caractΦres de MuLisp (SUBSTRING, FINDSTRING,
PACK, UNPACK, LENGTH, ASCII) ne sont pas implΘmentables avec OricLisp.
* les Θchappements de MuLisp (CATCH, THROW) ne sont pas implΘmentables avec
OricLisp, le traitement d'erreur non plus.
* les fonctions de lecture/Θcriture sur fichier ne sont pas disponibles avec
OricLisp.
* les puissantes macro-fonctions d'OricLisp n'existent pas avec MuLisp, si un
programme OricLisp les utilise intensivement, on prΘfΦrera alors porter vers
un autre dialecte comme LeLisp.
- 25 -
D. Annexe D: DΘtails d'implΘmentation
-------------------------------------
a) Carte mΘmoire
----------------
0000 ------------+----------------------------+
| variables systΦmes |
0100 ------------+----------------------------+
| quatre segments de pile |
0200 ------------+----------------------------+
| variables systΦmes |
0300 ------------+----------------------------+
| entrΘes-sorties |
0400 ------------+----------------------------+
| buffer d'entrΘe clavier |
| + zone de conversion |
0500 ------------+----------------------------+-------- BOTTOM
| pile des liaisons |
|\/\/\/\/\/\/\/\/\/\/\/\/\/\/|
| |
|/\/\/\/\/\/\/\/\/\/\/\/\/\/\|
| segments de pile virtuelle |
1000 ------------+----------------------------+-------- SYSTEM
| chaεnes de caractΦres |
|\/\/\/\/\/\/\/\/\/\/\/\/\/\/|
| |
|/\/\/\/\/\/\/\/\/\/\/\/\/\/\|
| paires pointΘes |
5000 ------------+----------------------------+-------- ATOMS
| nombres |
|\/\/\/\/\/\/\/\/\/\/\/\/\/\/|
| |
|/\/\/\/\/\/\/\/\/\/\/\/\/\/\|
| symboles |
8000 ------------+----------------------------+-------- TOPATOM
| noyau |
+----------------------------+
Les adresses BOTTOM, SYSTEM et ATOMS peuvent Ωtre modifiΘes pour s'adapter aux
consommations diffΘrentes en nombres et symboles, paires pointΘes ou rΘcursivitΘ
d'une application. Malheureusement, cette transformation doit se faire en
modifiant des adresses du noyau AVANT qu'il ne s'exΘcute...
La page de BOTTOM peut Ωtre changΘe en modifiant l'adresse 9AA6, la page de
SYSTEM α l'adresse 9AAA et la page d'ATOMS α l'adresse 9AB0.
b) Structures de donnΘes
------------------------
Les paires pointΘes ont la structure suivante:
+----------+------------+
| CAR | CDR |
+----------+------------+
Les symboles ont la structure suivante :
+----------+------------+----------+-------------+
| Valeur | PropriΘtΘs | Fonction | Nom externe |
+----------+------------+----------+-------------+
- 26 -
Le CAR d'un symbole donne donc sa valeur, un symbole n'ayant pas reτu de valeur
a pour valeur lui mΩme (la valeur pointe vers le mΩme symbole).
Le CDR d'un symbole porte la liste de propriΘtΘs associΘe au symbole, elle est
NIL par dΘfaut et peut Ωtre changΘe par RPLACD.
Le champ fonction du symbole est NIL si aucune fonction n'est associΘe, il
pointe vers la liste LAMBDA (ou FLAMBDA ou MLAMBDA) pour une fonction
utilisateur, et contient l'adresse de dΘbut pour une fonction en langage
machine (les deux derniers sont diffΘrenciΘs par le bit de poids fort, il est α
1 pour une routine en langage machine au delα de l'adresse 8000h et α 0 pour
une fonction utilisateur)
Le champ nom externe (ou Print-name) pointe vers la chaεne de caractΦres
reprΘsentant le symbole.
Les nombres ont la structure suivante:
+----------+------------+------------------------+
| Valeur | PropriΘtΘs | reprΘsentation 32 bits |
+----------+------------+------------------------+
Le CAR d'un nombre (sa valeur) pointe toujours sur lui-mΩme.
Le CDR d'un nombre (sa propriΘtΘ) est T s'il est positif ou nul, NIL si nΘgatif.
c) La pile virtuelle
--------------------
Le langage LISP est par dΘfinition rΘcursif et ceci pose problΦme pour
l'implΘmentation sur le processeur 6502 qui possΦde un pointeur de pile 8 bits.
OricLisp implΘmente une pile virtuelle inΘdite qui lui dispense de simuler une
pile 16 bits, et lui permet d'utiliser les instructions normales de pile du
6502 : JSR, RTS, PHA, PLA...
La pile virtuelle est constituΘe de segments de 64 octets, 4 segments sont
donc prΘsents dans la pile physique du 6502. De temps α autre (dans la routine
EVAL par exemple), la position du pointeur de pile est vΘrifiΘe afin de dΘtecter
si une frontiΦre de segment a ΘtΘ franchie. Ce franchissement peut provoquer la
sauvegarde d'un segment de la pile physique vers la pile virtuelle, ou la
rΘcupΘration d'un segment de la pile virtuelle, mais l'algorithme utilisΘ est
adaptΘ au parcours rΘcursif d'arbres binaires et fait que le transfert d'un
segment est peu courant (il n'y a pas de transfert lorsque le pointeur oscille
de part et d'autre d'une frontiΦre de segment).
d) Le ramasse-miettes
---------------------
Une premiΦre passe marque les paires et les atomes accessibles α partir des
champs valeur, propriΘtΘs et fonction des symboles (on ne marque pas un
symbole dont la valeur pointe sur lui-mΩme et qui n'a pas dΘfinition de
fonction), ou α partir de la pile des liaisons.
Une deuxiΦme passe supprime les paires, les symboles et les nombres non marquΘs
et les compacte.
Une troisiΦme passe enlΦve le marquage des objets et ajuste les pointeurs vers
des objets dΘplacΘs par le compactage.
Une derniΦre passe compacte les chaεnes des symboles qui n'ont pas ΘtΘ ΘliminΘs
par le ramasse-miettes.
- 27 -
E. Annexe E: Exemple du parcours du cavalier
--------------------------------------------
Le parcours du cavalier sur un Θchiquier est un problΦme classique qui consiste
α parcourir toutes les cases de l'Θchiquier une fois et une seule en respectant
la marche du cavalier aux Θchecs. LISP est bien adaptΘ α ce genre de recherche
de solution, en voici une rΘsolution assez concise pour un Θchiquier de taille
N quelconque:
; une liste des 8 sauts possibles du cavalier, classΘs par une heuristique :
(SETQ DIR '((-2 1)(-2 -1)(-1 -2)(1 -2)(2 -1)(2 1)(1 2)(-1 2)))
; d'abord une petite fonction qui construit la liste des entiers M,M-1...1
(PUTD REBOURS '(LAMBDA (M) (COND ((ZEROP M) NIL) ((CONS M (REBOURS (- M 1)))))))
; puis une autre qui teste si une case n'est pas en dehors de l'Θchiquier
(PUTD TEST '(LAMBDA (X Y) (AND (< -1 X) (< X N) (< -1 Y) (< Y N))))
; pour construire la liste des positions atteignables α partir de M
(PUTD SAUTS '(LAMBDA (PAS)
(COND ((NULL PAS) NIL)
((TEST (+ (CAAR PAS) (MOD (- M 1) N)) (+ (CADAR PAS) (/ (- M 1) N)))
(CONS (+ (+ M (CAAR PAS)) (* N (CADAR PAS))) (SAUTS (CDR PAS))))
((SAUTS (CDR PAS))))))
; on construit en effet une table de ces listes pour l'ensemble des cases
(PUTD TABLE '(LAMBDA (M)
(COND ((ZEROP M) NIL)
((CONS (CONS M (SAUTS DIR)) (TABLE (- M 1)))))
; maintenant le programme lui-mΩme: cherche un chemin par toutes les cases
(PUTD CHEMIN '(LAMBDA (CASES RESTE PARCOURS)
(COND ((ZEROP RESTE) PARCOURS) ; trouvΘ !
((NULL CASES) NIL) ; une impasse...
((MEMBER (CAR CASES) PARCOURS) ; dΘjα passΘ par ici...
(CHEMIN (CDR CASES) RESTE PARCOURS))
((CHEMIN (CDR (ASSOC (CAR CASES) TAB))
(- REST 1)
(CONS (CAR CASES) PARC))) ; on essaie par lα...
((CHEMIN (CDR CASES) REST PARC))))) ; et les autres aussi...
; un programme principal pour lancer le tout, et c'est fini !
(PUTD PARCOURS '(LAMBDA (N TAB)
(SETQ TAB (TABLE (* N N))) ; on calcule la table une seule fois
(CHEMIN (REBOURS (* N N)) (* N N)))) ; puis on cherche
Remarque: Pour trouver toutes les solutions, il suffit de remplacer la
premiΦre clause de la fonction chemin par :
(COND ((ZEROP RESTE) (PRINT PARCOURS) NIL)
Remarque2: Une image mΘmoire est fournie avec OricLisp, contenant une version
amΘliorΘe de ce programme pour trouver plus rapidement une solution.
- 28 -