www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

squash-lisp.lisp (18064B)


      1 (require 'mini-meval "mini-meval")
      2 (require 'match "match")
      3 
      4 (require 'squash-lisp-1 "squash-lisp-1")
      5 (require 'squash-lisp-1 "squash-lisp-3")
      6 
      7 ;; Notes sur le fonctionnement (théorique) de squash-lisp
      8 
      9 
     10 #|
     11 
     12 ;; Formes pouvant créer des variables capturables :
     13 lambda
     14 let
     15 let* // let imbriqués
     16 // progv // compliqué, pas très utile
     17 flet // let pour les fonctions
     18 labels // letrec pour les fonctions
     19 macrolet // letrec pour les macros
     20 // symbol-macrolet // compliqué, pas très utile
     21 
     22 ;; Formes pouvant capturer des variables :
     23 lambda
     24 defun => lambda
     25 
     26 ;; Comportement des variables globales et spéciales
     27 - une variable qui n'est pas attachée lexicalement est globale
     28 - une variable qui est déclarée speciale dans le cadre d'un let, defun, etc., est
     29   modifiée globallement par sa nouvelle valeur (celle du let / paramètre), puis
     30   sa valeur est restaurée à la fin du let / defun / ...
     31 - une variable qui est globalement spéciale (comme c'est le cas pour les variables defvar)
     32   a une seule valeur partagée entre toutes ses utilisations. Autrement dit, partout
     33   où cette variable est lue ou modifiée, c'est la même valeur qui est utilisée.
     34 
     35 (defvar x val)
     36 => (progn (proclaim '(special x))
     37           (unless (boundp 'x)
     38             (setq x val)))
     39 (boundp var)
     40 => t si var _globale_ est bound (on s'en fiche de son état lexical).
     41 
     42 ;; Comportement des closures
     43 Lorsqu'on fait une closure (à l'exécution donc), elle capture *toutes* les variables capturables de l'environnement.
     44 Les variables capturées ont ensuite leur valeur partagée entre les différentes closures qui les utilisent et "l'extérieur" (le lieu de déclaration initial des variables).
     45 Exemple :
     46 (defun introspect () nil)
     47 (defun make-closure (initial)
     48   (let ((a 1) (b 2) (c 3))
     49     (let ((closure-incf (lambda () (incf initial)))
     50           (closure-return (lambda () (introspect) initial)))
     51       (print initial)
     52       (funcall closure-incf)
     53       (print initial) ;; l'extérieur partage la même valeur
     54       (cons closure-incf closure-return))))
     55 (setq cl1 (make-closure 1))
     56 => 1
     57 => 2
     58 => (#<closure...> . #<closure...>)
     59 (setq cl42 (make-closure 42))
     60 => 42
     61 => 43
     62 => (#<closure...> . #<closure...>)
     63 ;; les valeurs sont partagées entre les closures créées sur les mêmes instances de variables
     64 (funcall (car cl1))
     65 => 3
     66 (funcall (cdr cl1))
     67 => 3
     68 ;; mais pas entre des closures créées à différents moments
     69 (funcall (cdr cl42))
     70 => 43
     71 
     72 Le comportement des fonctions et des macros expliqué ci-dessous permet de prouver que la capture s'effectue sur toutes les variables et non pas seulement celles qui paraissent être accessibles :
     73 (defmacro introspect-closure (get-variable closure)
     74   `(progn (defmacro introspect () '(print ,get-variable))
     75           (funcall ,closure)
     76           (defun introspect () nil)))
     77 (introspect-closure a (cdr cl1))
     78 => 1 ;; (print a)
     79 => 3
     80 (introspect-closure b (cdr cl1))
     81 => 2 ;; (print b)
     82 => 3
     83 (introspect-closure c (cdr cl1))
     84 => 3 ;; (print c)
     85 => 3
     86 (introspect-closure initial (cdr cl1))
     87 => 3 ;; (print intitial)
     88 => 3
     89 
     90 Un autre moyen de le vérifier est de mettre dans le let ((a 1) (b 2) (c 3)) un autre variable, non utilisée, qui est associée à une très grosse liste (un million d'éléments).
     91 Après avoir créé une vingtaine de closures, on voit dans "top" que clisp occupe environ 90 Mo de RAM, alors qu'auparavent il n'en occupait que très peu.
     92 Pourtant ces listes d'un million d'éléments semblent inaccessibles, sauf par notre trucage introspect-closure.
     93 
     94 ;; Comportement des fonctions et des macros
     95 Si une macro est rencontrée, elle est expansée
     96 Si un appel de fonction est rencontré, la fonction est appellée telle qu'elle
     97 Si une fonction est redéfinie en tant que macro, tous les appels de fonction qui lui correspondent sont transformés en appels de macro (expansion à la volée).
     98 On peut alors redéfinir la macro en macro ou en fonction, au choix, plusieurs fois, les appels suivent "intuitivement". (Ça existe encore ça l'intuition ?)
     99 Si une macro "rencontrée initialement" est redéfinie en tant que fonction, les appels qui ont déjà été "expansés initialement" ne sont pas redéfinis.
    100 Dans la structure suivante, la règle du "rencontrée initialement" est bien appliquée, la macro n'est pas ré-expansée :
    101 (defmacro mcr (x) `(list ',x 'y))
    102 (defun bar-maker () (defun bar () (mcr a)))
    103 (bar-maker)
    104 (bar)
    105 => (a y)
    106 (defmacro mcr (x) `(list ',x 'z))
    107 (bar)
    108 => (a y)
    109 (bar-maker)
    110 (bar)
    111 => (a y)
    112 
    113 ;; Décision
    114 
    115 Pour des raisons de santé mentale, d'efficacité et d'alignement des planètes, nous ne supporterons pas la redéfinition de fonctions en tant que macros.
    116 De même, si une macro est utilisée avant sa définition, elle ne sera pas expansée, et déclenchera probablement une erreur "undefined function".
    117 Et pour simplifier la compilation, toutes les définitions de macros seront prises en compte,
    118 qu'elles soient à l'intérieur d'un if, d'un defun, d'un let... sans prendre en compte leur environnement.
    119 
    120 ;; Fonctionnement des block et tagbody
    121 Les noms de blocs sont un peu comme des variables, ils sont capturés par les closures.
    122 Ainsi, dans l'exemple suivant, lorsque le (return-from a) est apellé, on sort directement
    123 du premier bloc a dans la pile d'appels.
    124 
    125 (defun foo (fn n)
    126   (format t "~&foo ~a ~a" n fn)
    127   (block a
    128     (bar (if fn fn (lambda (x)
    129                      (format t "~&lambda ~a" x)
    130                      (if (<= x 0) (return-from a 42))))
    131          n))
    132   (format t "~&foo2"))
    133 
    134 (defun bar (fn n)
    135   (format t "~&bar ~a ~a" n fn)
    136   (funcall fn n)
    137   (foo fn (- n 1))
    138   (format t "~&bar2"))
    139 
    140 ;; Choix d'implémentation des block / return-from, tagbody / go, catch / throw
    141 
    142 Lorsqu'on rentre dans un block, on met sur la pile un marqueur spécial avec un pointeur vers un objet créé à l'exécution.
    143 Les return-from <nom> qui sont accessibles lexicalement sont remplacés par un (unwind <l'objet>)
    144 Unwind remonte la pile jusqu'à trouver le marqueur spécial, tout en exécutant les unwind-protect éventuels.
    145 Si unwind ne trouve pas le marqueur et arrive en haut de la pile, il signale une erreur et termine le programme.
    146 Sinon, l'exécution reprend après le block.
    147 Le traitement de tagbody/go est similaire pour sortir d'un tag, puis on jmp directement sur le tag de destination (vu qu'il est au même niveau).
    148 Le traitement de catch/throw est similaire, sauf que le pointeur est simplement un pointeur vers l'objet utilisé pour le catch / throw.
    149 À noter que la liaison lexicale pour le block et le tagbody est à effectuer avant de sortir éventuellement des lambdas anonymes de leur fonction englobante.
    150 (comme la liaison lexicale ne s'effectue pas sur des variables, cette transformation ne présèrverait pas la liaison).
    151 Cette étape doit s'effectuer après l'expansion de macros, sinon on risque de louper des block / return-from / ... .
    152 
    153 ;; Choix d'implémentation des closures :
    154 Lorsqu'une variable est capturée, on ne peut pas copier directement l'adresse dans le tas de sa valeur pour pouvoir la manipuler,
    155 car il se peut que l'on doive déplacer la valeur si on souhaite la remplacer par quelque chose de plus gros (par ex. remplacer un nombre par une string).
    156 On est donc obligé d'avoir un pointeur d'indirection supplémentaire, de taille fixe, qui ne sera pas déplacé.
    157 Ce pointeur est ce qu'on appellera la closure-cell.
    158 
    159 Lorsqu'on rencontre un binding d'une variable (let, labels, lambda, ...), on regarde si elle est capturée à l'intérieur
    160 du corps du special-form qui effectue la liaison.
    161 Si c'est le cas, on crée une closure-cell, qui contient un pointeur vers l'endroit où est stockée la vraie valeur de la variable,
    162 et à chaque lecture / écriture de la variable, on utilise (get-closure-cell-value <cl-cell>) à la place.
    163 Ceci doit s'effectuer après l'expansion de macros, sinon on risque de louper des noms de variable capturées écrits par les macros.
    164 Chaque lambda capturant des variables est ensuite modifié de manière à prendre les closure-cell des variables capturées en paramètre.
    165 Il est "emballé" par une sorte de forme spéciale "closure", qui contient la liste des closure-cell à passer en paramètres à la lambda.
    166 Il n'y a alors plus de closures dans le sens où toutes les variables capturées le sont explicitement, et sont passées en paramètre.
    167 On peut donc "sortir" toutes les closures de leur environnement englobant, en les transformant en defuns nommés avec un symbole unique
    168 généré avec make-symbol. La lambda elle-même est alors remplacée par (closure symbole-unique-du-defun closure-cell*).
    169 
    170 TODO : revoir les choix d'implémentation des closures après une nuit de someil...
    171 Le but est de ne plus avoir aucun lambda imbriqué dans quoi que ce soit
    172 (tous les lambdas doivent être au top-level, juste emballés par un truc qui les nomme).
    173 
    174 ;; Implémentation des let, let*, flet, labels, macrolet, ...
    175 Vu que tous les lambda ont été ramenés au top-level, il n'y a plus de capture de variables.
    176 Les let sont donc :
    177  - À l'intérieur d'un lambda, quelque part
    178  - À l'intérieur d'un autre let qui est au top-level
    179  - À l'intérieur d'un progn qui est au top-level
    180  - Directement au top-level
    181 Les trois derniers cas peuvent être ramenés au premier en les emballant avec un lambda sans paramètres
    182 On peut alors "applatir" tous les let imbriqués dans un lambda dans la liste de paramètres du lambda.
    183 
    184 Au top-level, on aura donc uniquement des lambda nommés, avec ou sans paramètres, qui ne contiendront ni lambda ni aucune forme de let.
    185 Il n'y aura plus de macros.
    186 Plus de block ni tagbody, donc pas de liaison lexicale à ce niveau.
    187 
    188 Voici la liste des special-form.
    189 block                   OK
    190 catch                   OK
    191 declare                 -- ?
    192 eval-when              *OK Avant/pendant macro-expansion
    193 flet                   *OK
    194 function                -- ?
    195 generic-flet            ~~ Non implémenté
    196 generic-labels          ~~ Non implémenté
    197 go                      OK
    198 if                      -- À compiler
    199 labels                 *OK
    200 let                    *OK
    201 let*                   *OK
    202 macrolet               *OK
    203 multiple-value-call     ~~ Non implémenté
    204 multiple-value-prog1    ~~ Non implémenté (mais le serait avec une macro)
    205 progn                  *OK (un seul géant qui représente le top-level)
    206 progv                   ~~ Non implémenté
    207 quote                   -- À compiler
    208 return-from             OK
    209 setq                    -- À compiler
    210 symbol-macrolet         ~~ Non implémenté (peut-être ?)
    211 tagbody                 OK
    212 the                     ~~ Non implémenté, transformé ainsi : (the type form) => form
    213 throw                   OK
    214 unwind-protect          -- À compiler (ou bien macro-expansé en termes de "asm")
    215 with-added-methors      ~~ Non implémenté
    216 
    217 Les "formes spéciales du compilo" suivantes ont été rajoutées :
    218 asm                     -- À compiler
    219 unwind                  -- À compiler (ou bien macro-expansé en termes de "asm")
    220 closure                 -- À compiler
    221 + les appels de lambdas nommés. -- À compiler
    222 
    223 ;; Implémentation des macros et de eval-when
    224 Lors de la compilation d'un fichier, son top-level est traversé de la manière suivante :
    225 On crée une instance de compiler-meval, un mini-meval qui renvoie toujours
    226 un cons de la valeur de retour et de son état, pour qu'on puisse le rappeler.
    227 compiler-meval transforme le eval-when en progn si sa situation contient :execute, en nil sinon.
    228 
    229 NOTE : lorsqu'on rencontre la macro declaim au top-level, la proclamation est prise en compte.
    230 
    231 | - Si on rencontre un defmacro (au toplevel ou ailleurs).
    232 |   - On demande à compiler-meval de l'exécuter.
    233 | - Si on rencontre EVAL-WHEN,
    234 |   - Au top-level,
    235 |     - Pour chaque form du body,
    236 |       - Si la situation contient :compile-toplevel, le form est évalué dans compiler-meval.
    237 |       - Si la situation contient :load-toplevel, le form est compilé (après évaluation dans compiler-meval s'il y a lieu).
    238 |       - Lorsqu'un eval-when au top-level contient des eval-when directement sous lui, ils sont traités comme s'ils étaient directement au top-level.
    239 |   - Ailleurs
    240 |     - Si la situation contient :load-toplevel, le eval-when est remplacé par son body (TODO : À VÉRIVFIER !).
    241 | - Si on rencontre un macrolet
    242 |   - On fait une copie de l'état de compiler-meval
    243 |   - On lui demande d'exécuter les définitions
    244 |   - On évalue le body avec ce nouvel état
    245 |   - On continue avec l'ancien état
    246 | - Si on gère le symbol-macrolet
    247 |   - Le fonctionnement est le même que pour le macrolet
    248 |   - Lorsqu'on rencontre un symbole, on regarde s'il a une définition de type symbol-macrolet
    249 | - Si on rencontre une macro définie dans l'environnement de compiler-meval,
    250 |   1) On demande à compiler-meval d'expanser la macro sur un niveau.
    251 |   2) On re-lance la transformation (eval-when / defmacro / appel de macro / ...) sur le résultat s'il y a a eu expansion.
    252 | - S'occuper du cas du lambda et des autres mot-clés bizarres (ne pas faire de macro-expansion dessus).
    253 | - Dans les autres cas, on transforme récursivement l'expression.
    254 
    255 ;; Comportement des variables globales et spéciales
    256 Lorsqu'une variable est utilisée mais ne correspond à aucune liaison (établie par let, …), cette utilisation fait référence
    257 à une liaison "globale" de cette variable (autrement dit, la valeur est partagée entre toutes les utilisations sans liaison).
    258 Par défaut, une variable globale est "unbound", et n'a donc pas de valeur. La lecture d'une variable unbound est une erreur.
    259 x
    260 => erreur
    261 (defun bar () x)
    262 (bar)
    263 => erreur
    264 (setq x 3)
    265 (bar)
    266 => 3
    267 
    268 Lorsqu'une variable est déclarée localement spéciale, avec (declare (special nom-de-variable)) au début d'un defun, d'un let etc.,
    269 la valeur de la variable est alors la même que la valeur globale.
    270 Ce comportement spécial s'applique là où la variable est dans la portée lexicale de la forme spéciale englobant le declare (donc uniquement dans le defun, let, …).
    271 
    272 (defun baz () y)
    273 (let ((y 3)) (let ((z 1)) (declare (special y)) (setq y 42) (baz)))
    274 => 42 ;; Bien que y soit une liaison lexicale à cause du (let ((y 3)), le (special y) le transforme en variable globale.
    275 y
    276 => 42
    277 
    278 Si la forme spéciale englobante devait créer une liaison lecicale pour cette variable, elle ne le fait pas, mais à la place,
    279  - la valeur globale d'origine de la variable est sauvegardée,
    280  - sa valeur globale est modifiée avec la nouvelle valeur (paramètre effectif pour un defun, valeur de droite pour un let, ...)
    281  - La variable devient boundp si elle ne l'était pas déjà
    282  - le corps est exécuté, avec la variable partageant sa valeur avec la varaible globale
    283  - la valeur d'origine de la variable globale est restaurée.
    284  - Si la valeur était unbound, elle redevient unbound.
    285 
    286 (defun quux () (print (boundp 'w)) w)
    287 (boundp 'w)
    288 => NIL
    289 (quux)
    290 => erreur
    291 (let ((w 3)) (declare (special w)) (print (boundp 'w)) (quux))
    292 => T ;; boundp
    293 => T ;; boundp
    294 => 3
    295 (boundp 'w)
    296 (quux)
    297 => erreur ;; La valeur est bien restaurée.
    298 
    299 Lorsqu'on effectue un (defvar var val), var devient globalement spéciale : toutes les utilisations de var pointent vers la même valeur,
    300 y compris les utilisations effectuées avant le defvar.
    301 (defun foo1 () var)
    302 (defun foo2 () (let ((var 4)) (print var) (foo1)))
    303 var
    304 => erreur
    305 (foo1)
    306 => erreur
    307 (foo2)
    308 => 4
    309 => erreur
    310 (defvar var 123)
    311 var
    312 => 123
    313 (foo1)
    314 => 123
    315 (foo2)
    316 => 4
    317 => 4
    318 
    319 Lors du defvar, si la variable est boundp, sa valeur était conservée, sinon sa valeur globale devient la valeur spécifiée par le defvar.
    320 Notemment, si le defvar apparaît à l'intérieur d'un let-special qui rend la variable boundp locallement, sa valeur globale sera restaurée à unbound à la sortie du let.
    321 (defun get-1 () not-boundp)
    322 (defun get-2 () is-boundp)
    323 (defun get-3 () locally-boundp)
    324 (defun getlet-1 () (let ((not-boundp 123))     (get-1)))
    325 (defun getlet-2 () (let ((is-boundp 123))      (get-2)))
    326 (defun getlet-3 () (let ((locally-boundp 123)) (get-3)))
    327 (setq is-boundp 42)
    328 (get-1)    => error ;; not-boundp
    329 (get-2)    => 42    ;; is-boundp
    330 (get-3)    => error ;; locally-boundp
    331 (getlet-1) => error ;; not-boundp
    332 (getlet-2) => 42    ;; is-boundp
    333 (getlet-3) => error ;; locally-boundp
    334 
    335 (defvar not-boundp 3)
    336 (defvar is-boundp  3)
    337 (let ((locally-boundp 42))
    338   (declare (special locally-boundp))
    339   (defvar locally-boundp 3))
    340 (get-1)    => 3     ;; not-boundp
    341 (get-2)    => 42    ;; is-boundp
    342 (get-3)    => error ;; locally-boundp
    343 ;; La variable est maintenant spéciale partout :
    344 (getlet-1) => 123   ;; not-boundp
    345 (getlet-2) => 123   ;; is-boundp
    346 (getlet-3) => 123   ;; locally-boundp
    347 
    348 ;; Implémentation des variables globales et spéciales
    349 
    350 Pour les mêmes raisons de santé d'esprit et d'efficacité et d'alignement des planètes que pour la redéclaration de fonctions en macros, nous ne supporterons pas la redéclaration
    351 de variables non spéciales en spéciales. Ainsi, si un defvar apparaît *après* des utilisations non spéciales de la variable, ces utilisations resteront non spéciales.
    352 
    353 Lorsqu'une variable est détectée comme étant spéciale (soit globalement, avec defvar, soit localement, avec declare), sa valeur est stockée dans une global-cell,
    354 qui ressemble comme deux goutes d'eau à une closure-cell, et toutes ces utilisations passent par la globla-cell, comme pour les variables capturées.
    355 On est obligé d'avoir ce niveau d'indirection pour les mêmes raisons que pour le closure-cell.
    356 La différence avec une closure-cell est qu'il n'y a qu'une seule global-cell par variable, qui est créée à la compilation.
    357 De même, l'utilisation globale d'une variable de manière globale est remplacée par une référence à sa global-cell.
    358 De plus, les formes spéciales qui devaient créer une liaison locale sont transformées comme suit :
    359 (let ((x 3)) (declare (special x)) (setq x 42) x)
    360 Est transformé en :
    361 == En-tête
    362 [global-cell x]
    363 #<unbound>
    364 == Code
    365 [push [get-global-cell-value x]]
    366 [set-global-cell-value x 3]
    367 [set-global-cell-value x 42]
    368 [get-global-cell-value x]
    369 [set-global-cell-value x [pop]]
    370 
    371 |#
    372 
    373 (provide 'squash-lisp)