Import des modifications de la branche compilation-georges.
This commit is contained in:
parent
ccf304f19a
commit
cc109f9c5a
|
@ -2,7 +2,8 @@ TODO :
|
|||
- Ajouter la fonction map-case-analysis
|
||||
- Changer les <signal> par les fonctions warn (warning ?) ou error selon le cas d'utilisation.
|
||||
- Remplacer les ";; cas machin" par le code effectif.
|
||||
|
||||
- copy-all doit gérer les structures circulaires
|
||||
- modifier run-tests pour qu'il renvoie la syntaxe d'un appel de macro qui ferait les let des deftestvar correctement (sans appel à eval).
|
||||
|
||||
Questions :
|
||||
- Le prof a dit qu'on ne devait pas gérer le tas, donc pas d'affectations (setf, ...).
|
||||
|
|
BIN
implementation/capture-variables.dia
Normal file
BIN
implementation/capture-variables.dia
Normal file
Binary file not shown.
138
implementation/compilation.lisp
Normal file
138
implementation/compilation.lisp
Normal file
|
@ -0,0 +1,138 @@
|
|||
(load "match")
|
||||
(load "util")
|
||||
(load "implementation/lisp2cli")
|
||||
|
||||
(defvar asm-fixnum-size 32)
|
||||
(defvar asm-max-fixnum (expt 2 asm-fixnum-size))
|
||||
(defun type-number (type)
|
||||
(position type '(fixnum bignum symbol string cons nil)))
|
||||
(defvar label-ctr 0)
|
||||
|
||||
(defmacro fasm (&rest stuff)
|
||||
`(format nil ,@stuff))
|
||||
(defun db-type (type)
|
||||
(fasm "db ~a" (type-number type)))
|
||||
|
||||
;; My-compile
|
||||
|
||||
(defvar result-asm nil)
|
||||
(defvar sections '(data code))
|
||||
|
||||
(defun real-asm-block (section label body)
|
||||
(when (not (member section sections))
|
||||
(error "Section assembleur inconnue : ~w" section))
|
||||
(push (format nil "section .~w" section) result-asm)
|
||||
(push (format nil "~a:" label) result-asm)
|
||||
(mapcar (lambda (x) (push x result-asm)) body)
|
||||
label)
|
||||
|
||||
(defun asm-block (section label-base &rest body)
|
||||
(real-asm-block
|
||||
section
|
||||
(format nil "~a-~a" label-base (incf label-ctr))
|
||||
body))
|
||||
|
||||
(defvar asm-once nil)
|
||||
(defun asm-once (section label &rest body)
|
||||
(unless (member label asm-once :test #'string-equal)
|
||||
(push label asm-once)
|
||||
(real-asm-block section label body))
|
||||
label)
|
||||
|
||||
(defmacro my-compile (expr)
|
||||
`(progn (setq result-asm nil)
|
||||
(setq asm-once nil)
|
||||
(my-compile-1 `(:main ,(lisp2cli ',expr)))
|
||||
(format nil "~&~{~%~a~}" (flatten (reverse result-asm)))))
|
||||
|
||||
;;; Règles de compilation
|
||||
|
||||
(defmatch my-compile-1)
|
||||
|
||||
;; fixnum
|
||||
(defmatch my-compile-1 (:nil :const :num . (? numberp (< x asm-max-fixnum)))
|
||||
(asm-block 'data "fixnum-constant"
|
||||
(db-type 'fixnum)
|
||||
(fasm "db ~a" num)))
|
||||
|
||||
;; bignum
|
||||
(defmatch my-compile-1 (:nil :const :num . (? numberp (>= x asm-max-fixnum)))
|
||||
(asm-block 'data "bignum-constant"
|
||||
(db-type 'bignum)
|
||||
(let ((lst (split-bytes num asm-fixnum-size)))
|
||||
(fasm "~{~&db ~a~}" (cons (length lst) lst)))))
|
||||
|
||||
;; string
|
||||
(defmatch my-compile-1 (:nil :const :str . (? stringp))
|
||||
(asm-block 'data "string-constant"
|
||||
(db-type 'string)
|
||||
(fasm "db ~a" (length str))
|
||||
(fasm "~{~&db ~a~}" (map 'list #'char-code str))))
|
||||
|
||||
;; symbol
|
||||
(defmatch my-compile-1 (:nil :const :sym . (? symbolp))
|
||||
(asm-once 'data (format nil "symbol-~w" sym)
|
||||
(db-type 'symbol)
|
||||
(fasm "db @~a" (my-compile-1 (string sym)))))
|
||||
|
||||
;; cons
|
||||
(defmatch my-compile-1 (:nil :const . (:car _ :cdr . _))
|
||||
(asm-block 'data "cons-cell-constant"
|
||||
(db-type 'cons)
|
||||
(fasm "db @~a" (my-compile-1 `(:const . ,car)))
|
||||
(fasm "db @~a" (my-compile-1 `(:const . ,cdr)))))
|
||||
|
||||
(defun compile-get-val (cli)
|
||||
(if (match (:nil :const . _) cli)
|
||||
(list (fasm "load @~a r0" (my-compile-1 cli))
|
||||
(fasm "push r0"))
|
||||
(list (my-compile-1 cli)
|
||||
(fasm "push r0"))))
|
||||
|
||||
;; call
|
||||
(defmatch my-compile-1 (:nil :call :name _ :params . _)
|
||||
(list
|
||||
(mapcar #'compile-get-val params)
|
||||
(fasm "push ~a" (length params))
|
||||
(fasm "jsr function-~a" name)))
|
||||
|
||||
;; main
|
||||
(defmatch my-compile-1 (:nil :main :body _*)
|
||||
(asm-once 'code "main"
|
||||
(mapcar #'my-compile-1 body)))
|
||||
|
||||
;;; Exemples
|
||||
|
||||
(my-compile '(1 2 3))
|
||||
|
||||
(my-compile 3)
|
||||
;; section .data
|
||||
;; fixnum-constant-1
|
||||
;; db 0
|
||||
;; db 3
|
||||
|
||||
(my-compile (+ 2 3))
|
||||
;; =>
|
||||
;; section .data
|
||||
;; fixnum-constant-1:
|
||||
;; db 1
|
||||
;; db 2
|
||||
;; section .data
|
||||
;; fixnum-constant-2:
|
||||
;; db 1
|
||||
;; db 3
|
||||
;; section .code
|
||||
;; code-1:
|
||||
;; load @global-1 r0
|
||||
;; push r0
|
||||
;; load @global-2 r0
|
||||
;; push r0
|
||||
;; push 2
|
||||
;; jsr @fn-+
|
||||
;; retn
|
||||
;; section .text
|
||||
;; :fn-+
|
||||
;; pop r1
|
||||
;; pop r0
|
||||
;; add r1 r0
|
||||
;; retn
|
374
implementation/lisp2cli.lisp
Normal file
374
implementation/lisp2cli.lisp
Normal file
|
@ -0,0 +1,374 @@
|
|||
;; lisp2li simpliste pour le compilateur. On fusionnera les deux plus tard.
|
||||
|
||||
(defmatch lisp2cli)
|
||||
|
||||
(defmatch lisp2cli (:num . (? numberp)) `(:const . ,num))
|
||||
(defmatch lisp2cli (:str . (? stringp)) `(:const . ,str))
|
||||
(defmatch lisp2cli (quote :val _) `(:const . ,val))
|
||||
(defmatch lisp2cli () `(:const . nil))
|
||||
(defmatch lisp2cli (let ((:name $ :value _)*) :body _*)
|
||||
`(:let ,name ,value ,body))
|
||||
(defmatch lisp2cli (:name _ :params _*) `(:call ,name ,@(mapcar #'lisp2cli params)))
|
||||
(defmatch lisp2cli (:x . _) (error "Lisp2cli ne sait pas gérer : ~w" x))
|
||||
|
||||
|
||||
#|
|
||||
|
||||
;; Formes pouvant créer des variables capturables :
|
||||
lambda
|
||||
let
|
||||
let* // let imbriqués
|
||||
// progv // compliqué, pas très utile
|
||||
flet // let pour les fonctions
|
||||
labels // letrec pour les fonctions
|
||||
macrolet // letrec pour les macros
|
||||
// symbol-macrolet // compliqué, pas très utile
|
||||
|
||||
;; Formes pouvant capturer des variables :
|
||||
lambda
|
||||
defun => lambda
|
||||
|
||||
;; Comportement des variables globales et spéciales
|
||||
- une variable qui n'est pas attachée lexicalement est globale
|
||||
- une variable qui est déclarée speciale dans le cadre d'un let, defun, etc., est
|
||||
modifiée globallement par sa nouvelle valeur (celle du let / paramètre), puis
|
||||
sa valeur est restaurée à la fin du let / defun / ...
|
||||
- une variable qui est globalement spéciale (comme c'est le cas pour les variables defvar)
|
||||
a une seule valeur partagée entre toutes ses utilisations. Autrement dit, partout
|
||||
où cette variable est lue ou modifiée, c'est la même valeur qui est utilisée.
|
||||
|
||||
(defvar x val)
|
||||
=> (progn (proclaim '(special x))
|
||||
(unless (boundp 'x)
|
||||
(setq x val)))
|
||||
(boundp var)
|
||||
=> t si var _globale_ est bound (on s'en fiche de son état lexical).
|
||||
|
||||
;; Comportement des closures
|
||||
Lorsqu'on fait une closure (à l'exécution donc), elle capture *toutes* les variables capturables de l'environnement.
|
||||
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).
|
||||
Exemple :
|
||||
(defun introspect () nil)
|
||||
(defun make-closure (initial)
|
||||
(let ((a 1) (b 2) (c 3))
|
||||
(let ((closure-incf (lambda () (incf initial)))
|
||||
(closure-return (lambda () (introspect) initial)))
|
||||
(print initial)
|
||||
(funcall closure-incf)
|
||||
(print initial) ;; l'extérieur partage la même valeur
|
||||
(cons closure-incf closure-return))))
|
||||
(setq cl1 (make-closure 1))
|
||||
=> 1
|
||||
=> 2
|
||||
=> (#<closure...> . #<closure...>)
|
||||
(setq cl42 (make-closure 42))
|
||||
=> 42
|
||||
=> 43
|
||||
=> (#<closure...> . #<closure...>)
|
||||
;; les valeurs sont partagées entre les closures créées sur les mêmes instances de variables
|
||||
(funcall (car cl1))
|
||||
=> 3
|
||||
(funcall (cdr cl1))
|
||||
=> 3
|
||||
;; mais pas entre des closures créées à différents moments
|
||||
(funcall (cdr cl42))
|
||||
=> 43
|
||||
|
||||
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 :
|
||||
(defmacro introspect-closure (get-variable closure)
|
||||
`(progn (defmacro introspect () '(print ,get-variable))
|
||||
(funcall ,closure)))
|
||||
(introspect-closure a (cdr cl1))
|
||||
=> 1 ;; (print a)
|
||||
=> 3
|
||||
(introspect-closure b (cdr cl1))
|
||||
=> 2 ;; (print b)
|
||||
=> 3
|
||||
(introspect-closure c (cdr cl1))
|
||||
=> 3 ;; (print c)
|
||||
=> 3
|
||||
(introspect-closure initial (cdr cl1))
|
||||
=> 3 ;; (print intitial)
|
||||
=> 3
|
||||
|
||||
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).
|
||||
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.
|
||||
Pourtant ces listes d'un million d'éléments semblent inaccessibles, sauf par notre trucage introspect-closure.
|
||||
|
||||
;; Comportement des fonctions et des macros
|
||||
Si une macro est rencontrée, elle est expansée
|
||||
Si un appel de fonction est rencontré, la fonction est appellée telle qu'elle
|
||||
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). 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 ?)
|
||||
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.
|
||||
Dans la structure suivante, la règle du "rencontrée initialement" est bien appliquée, la macro n'est pas ré-expansée :
|
||||
(defmacro mcr (x) `(list ',x 'y))
|
||||
(defun bar-maker () (defun bar () (mcr a)))
|
||||
(bar-maker)
|
||||
(bar)
|
||||
=> (a y)
|
||||
(defmacro mcr (x) `(list ',x 'z))
|
||||
(bar)
|
||||
=> (a y)
|
||||
(bar-maker)
|
||||
(bar)
|
||||
=> (a y)
|
||||
|
||||
;; Décision
|
||||
|
||||
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.
|
||||
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".
|
||||
Et pour simplifier la compilation, toutes les définitions de macros seront prises en compte,
|
||||
qu'elles soient à l'intérieur d'un if, d'un defun, d'un let... sans prendre en compte leur environnement.
|
||||
|
||||
;; Fonctionnement des block et tagbody
|
||||
Les noms de blocs sont un peu comme des variables, ils sont capturés par les closures.
|
||||
Ainsi, dans l'exemple suivant, lorsque le (return-from a) est apellé, on sort directement
|
||||
du premier bloc a dans la pile d'appels.
|
||||
|
||||
(defun foo (fn n)
|
||||
(format t "~&foo ~a ~a" n fn)
|
||||
(block a
|
||||
(bar (if fn fn (lambda (x)
|
||||
(format t "~&lambda ~a" x)
|
||||
(if (<= x 0) (return-from a 42))))
|
||||
n))
|
||||
(format t "~&foo2"))
|
||||
|
||||
(defun bar (fn n)
|
||||
(format t "~&bar ~a ~a" n fn)
|
||||
(funcall fn n)
|
||||
(foo fn (- n 1))
|
||||
(format t "~&bar2"))
|
||||
|
||||
;; Choix d'implémentation des block / return-from, tagbody / go, catch / throw
|
||||
|
||||
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.
|
||||
Les return-from <nom> qui sont accessibles lexicalement sont remplacés par un (unwind <l'objet>)
|
||||
Unwind remonte la pile jusqu'à trouver le marqueur spécial, tout en exécutant les unwind-protect éventuels.
|
||||
Si unwind ne trouve pas le marqueur et arrive en haut de la pile, il signale une erreur et termine le programme.
|
||||
Sinon, l'exécution reprend après le block.
|
||||
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).
|
||||
Le traitement de catch/throw est similaire, sauf que le pointeur est simplement un pointeur vers l'objet utilisé pour le catch / throw.
|
||||
À noter que la liaison lexicale pour le block et le tagbody est à effectuer avant de sortir éventuellement des lambdas anonymes de leur fonction englobante.
|
||||
(comme la liaison lexicale ne s'effectue pas sur des variables, cette transformation ne présèrverait pas la liaison).
|
||||
Cette étape doit s'effectuer après l'expansion de macros, sinon on risque de louper des block / return-from / ... .
|
||||
|
||||
;; Choix d'implémentation des closures :
|
||||
Lorsqu'une variable est capturée, on ne peut pas copier directement l'adresse dans le tas de sa valeur pour pouvoir la manipuler,
|
||||
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).
|
||||
On est donc obligé d'avoir un pointeur d'indirection supplémentaire, de taille fixe, qui ne sera pas déplacé.
|
||||
Ce pointeur est ce qu'on appellera la closure-cell.
|
||||
|
||||
Lorsqu'on rencontre un binding d'une variable (let, labels, lambda, ...), on regarde si elle est capturée à l'intérieur
|
||||
du corps du special-form qui effectue la liaison.
|
||||
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,
|
||||
et à chaque lecture / écriture de la variable, on utilise (get-closure-cell-value <cl-cell>) à la place.
|
||||
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.
|
||||
Chaque lambda capturant des variables est ensuite modifié de manière à prendre les closure-cell des variables capturées en paramètre.
|
||||
Il est "emballé" par une sorte de forme spéciale "closure", qui contient la liste des closure-cell à passer en paramètres à la lambda.
|
||||
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.
|
||||
On peut donc "sortir" toutes les closures de leur environnement englobant, en les transformant en defuns nommés avec un symbole unique
|
||||
généré avec make-symbol. La lambda elle-même est alors remplacée par (closure symbole-unique-du-defun closure-cell*).
|
||||
|
||||
TODO : revoir les choix d'implémentation des closures après une nuit de someil...
|
||||
Le but est de ne plus avoir aucun lambda imbriqué dans quoi que ce soit
|
||||
(tous les lambdas doivent être au top-level, juste emballés par un truc qui les nomme).
|
||||
|
||||
;; Implémentation des let, let*, flet, labels, macrolet, ...
|
||||
Vu que tous les lambda ont été ramenés au top-level, il n'y a plus de capture de variables.
|
||||
Les let sont donc :
|
||||
- À l'intérieur d'un lambda, quelque part
|
||||
- À l'intérieur d'un autre let qui est au top-level
|
||||
- À l'intérieur d'un progn qui est au top-level
|
||||
- Directement au top-level
|
||||
Les trois derniers cas peuvent être ramenés au premier en les emballant avec un lambda sans paramètres
|
||||
On peut alors "applatir" tous les let imbriqués dans un lambda dans la liste de paramètres du lambda.
|
||||
|
||||
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.
|
||||
Il n'y aura plus de macros.
|
||||
Plus de block ni tagbody, donc pas de liaison lexicale à ce niveau.
|
||||
|
||||
Voici la liste des special-form.
|
||||
block OK
|
||||
catch OK
|
||||
declare -- ?
|
||||
eval-when *OK Avant/pendant macro-expansion
|
||||
flet *OK
|
||||
function -- ?
|
||||
generic-flet ~~ Non implémenté
|
||||
generic-labels ~~ Non implémenté
|
||||
go OK
|
||||
if -- À compiler
|
||||
labels *OK
|
||||
let *OK
|
||||
let* *OK
|
||||
macrolet *OK
|
||||
multiple-value-call ~~ Non implémenté
|
||||
multiple-value-prog1 ~~ Non implémenté (mais le serait avec une macro)
|
||||
progn *OK (un seul géant qui représente le top-level)
|
||||
progv ~~ Non implémenté
|
||||
quote -- À compiler
|
||||
return-from OK
|
||||
setq -- À compiler
|
||||
symbol-macrolet ~~ Non implémenté (peut-être ?)
|
||||
tagbody OK
|
||||
the ~~ Non implémenté, transformé ainsi : (the type form) => form
|
||||
throw OK
|
||||
unwind-protect -- À compiler (ou bien macro-expansé en termes de "asm")
|
||||
with-added-methors ~~ Non implémenté
|
||||
|
||||
Les "formes spéciales du compilo" suivantes ont été rajoutées :
|
||||
asm -- À compiler
|
||||
unwind -- À compiler (ou bien macro-expansé en termes de "asm")
|
||||
closure -- À compiler
|
||||
+ les appels de lambdas nommés. -- À compiler
|
||||
|
||||
;; Implémentation des macros et de eval-when
|
||||
Lors de la compilation d'un fichier, son top-level est traversé de la manière suivante :
|
||||
On crée une instance de compiler-meval, un mini-meval qui renvoie toujours
|
||||
un cons de la valeur de retour et de son état, pour qu'on puisse le rappeler.
|
||||
compiler-meval transforme le eval-when en progn si sa situation contient :execute, en nil sinon.
|
||||
|
||||
NOTE : lorsqu'on rencontre la macro declaim au top-level, la proclamation est prise en compte.
|
||||
|
||||
- Si on rencontre EVAL-WHEN,
|
||||
- Au top-level,
|
||||
- Pour chaque form du body,
|
||||
- Si la situation contient :compile-toplevel, le form est évalué dans compiler-meval.
|
||||
- Si la situation contient :load-toplevel, le form est compilé (après évaluation dans compiler-meval s'il y a lieu).
|
||||
- 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.
|
||||
- Ailleurs
|
||||
- Si la situation contient :load-toplevel, le eval-when est remplacé par son body (TODO : À VÉRIVFIER !).
|
||||
- Si on rencontre un defmacro
|
||||
- On demande à compiler-meval de l'exécuter. TODO : doit-on le faire uniquement au top-level ?.
|
||||
- Si on rencontre un macrolet
|
||||
- On fait une copie de l'état de compiler-meval
|
||||
- On lui demande d'exécuter les définitions
|
||||
- On évalue le body avec ce nouvel état
|
||||
- On continue avec l'ancien état
|
||||
- Si on gère le symbol-macrolet
|
||||
- Le fonctionnement est le même que pour le macrolet
|
||||
- Lorsqu'on rencontre un symbole, on regarde s'il a une définition de type symbol-macrolet
|
||||
- Si on rencontre une macro définie dans l'environnement de compiler-meval,
|
||||
1) On demande à compiler-meval d'expanser la macro sur un niveau.
|
||||
2) On re-lance la transformation (eval-when / defmacro / appel de macro / ...) sur le résultat s'il y a a eu expansion.
|
||||
- S'occuper du cas du lambda et des autres mot-clés bizarres (ne pas faire de macro-expansion dessus).
|
||||
- Dans les autres cas, on transforme récursivement l'expression.
|
||||
|
||||
;; Comportement des variables globales et spéciales
|
||||
Lorsqu'une variable est utilisée mais ne correspond à aucune liaison (établie par let, …), cette utilisation fait référence
|
||||
à une liaison "globale" de cette variable (autrement dit, la valeur est partagée entre toutes les utilisations sans liaison).
|
||||
Par défaut, une variable globale est "unbound", et n'a donc pas de valeur. La lecture d'une variable unbound est une erreur.
|
||||
x
|
||||
=> erreur
|
||||
(defun bar () x)
|
||||
(bar)
|
||||
=> erreur
|
||||
(setq x 3)
|
||||
(bar)
|
||||
=> 3
|
||||
|
||||
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.,
|
||||
la valeur de la variable est alors la même que la valeur globale.
|
||||
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, …).
|
||||
|
||||
(defun baz () y)
|
||||
(let ((y 3)) (let ((z 1)) (declare (special y)) (setq y 42) (baz)))
|
||||
=> 42 ;; Bien que y soit une liaison lexicale à cause du (let ((y 3)), le (special y) le transforme en variable globale.
|
||||
y
|
||||
=> 42
|
||||
|
||||
Si la forme spéciale englobante devait créer une liaison lecicale pour cette variable, elle ne le fait pas, mais à la place,
|
||||
- la valeur globale d'origine de la variable est sauvegardée,
|
||||
- sa valeur globale est modifiée avec la nouvelle valeur (paramètre effectif pour un defun, valeur de droite pour un let, ...)
|
||||
- La variable devient boundp si elle ne l'était pas déjà
|
||||
- le corps est exécuté, avec la variable partageant sa valeur avec la varaible globale
|
||||
- la valeur d'origine de la variable globale est restaurée.
|
||||
- Si la valeur était unbound, elle redevient unbound.
|
||||
|
||||
(defun quux () (print (boundp 'w)) w)
|
||||
(boundp 'w)
|
||||
=> NIL
|
||||
(quux)
|
||||
=> erreur
|
||||
(let ((w 3)) (declare (special w)) (print (boundp 'w)) (quux))
|
||||
=> T ;; boundp
|
||||
=> T ;; boundp
|
||||
=> 3
|
||||
(boundp 'w)
|
||||
(quux)
|
||||
=> erreur ;; La valeur est bien restaurée.
|
||||
|
||||
Lorsqu'on effectue un (defvar var val), var devient globalement spéciale : toutes les utilisations de var pointent vers la même valeur,
|
||||
y compris les utilisations effectuées avant le defvar.
|
||||
(defun foo1 () var)
|
||||
(defun foo2 () (let ((var 4)) (print var) (foo1)))
|
||||
var
|
||||
=> erreur
|
||||
(foo1)
|
||||
=> erreur
|
||||
(foo2)
|
||||
=> 4
|
||||
=> erreur
|
||||
(defvar var 123)
|
||||
var
|
||||
=> 123
|
||||
(foo1)
|
||||
=> 123
|
||||
(foo2)
|
||||
=> 4
|
||||
=> 4
|
||||
|
||||
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.
|
||||
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.
|
||||
(defun get-1 () not-boundp)
|
||||
(defun get-2 () is-boundp)
|
||||
(defun get-3 () locally-boundp)
|
||||
(defun getlet-1 () (let ((not-boundp 123)) (get-1)))
|
||||
(defun getlet-2 () (let ((is-boundp 123)) (get-2)))
|
||||
(defun getlet-3 () (let ((locally-boundp 123)) (get-3)))
|
||||
(setq is-boundp 42)
|
||||
(get-1) => error ;; not-boundp
|
||||
(get-2) => 42 ;; is-boundp
|
||||
(get-3) => error ;; locally-boundp
|
||||
(getlet-1) => error ;; not-boundp
|
||||
(getlet-2) => 42 ;; is-boundp
|
||||
(getlet-3) => error ;; locally-boundp
|
||||
|
||||
(defvar not-boundp 3)
|
||||
(defvar is-boundp 3)
|
||||
(let ((locally-boundp 42))
|
||||
(declare (special locally-boundp))
|
||||
(defvar locally-boundp 3))
|
||||
(get-1) => 3 ;; not-boundp
|
||||
(get-2) => 42 ;; is-boundp
|
||||
(get-3) => error ;; locally-boundp
|
||||
;; La variable est maintenant spéciale partout :
|
||||
(getlet-1) => 123 ;; not-boundp
|
||||
(getlet-2) => 123 ;; is-boundp
|
||||
(getlet-3) => 123 ;; locally-boundp
|
||||
|
||||
;; Implémentation des variables globales et spéciales
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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,
|
||||
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.
|
||||
On est obligé d'avoir ce niveau d'indirection pour les mêmes raisons que pour le closure-cell.
|
||||
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.
|
||||
De même, l'utilisation globale d'une variable de manière globale est remplacée par une référence à sa global-cell.
|
||||
De plus, les formes spéciales qui devaient créer une liaison locale sont transformées comme suit :
|
||||
(let ((x 3)) (declare (special x)) (setq x 42) x)
|
||||
Est transformé en :
|
||||
== En-tête
|
||||
[global-cell x]
|
||||
#<unbound>
|
||||
== Code
|
||||
[push [get-global-cell-value x]]
|
||||
[set-global-cell-value x 3]
|
||||
[set-global-cell-value x 42]
|
||||
[get-global-cell-value x]
|
||||
[set-global-cell-value x [pop]]
|
||||
|
||||
|#
|
238
implementation/mini-meval.lisp
Normal file
238
implementation/mini-meval.lisp
Normal file
|
@ -0,0 +1,238 @@
|
|||
(load "match")
|
||||
(load "util")
|
||||
|
||||
(defun slice-up-lambda-list (lambda-list)
|
||||
(match-automaton lambda-list fixed
|
||||
(fixed accept)
|
||||
(fixed optional &optional)
|
||||
(fixed rest &rest)
|
||||
(fixed key &key)
|
||||
(fixed aux &aux)
|
||||
(fixed reject $&)
|
||||
(fixed fixed (:var . $$) var)
|
||||
(optional accept)
|
||||
(optional rest &rest)
|
||||
(optional key &key)
|
||||
(optional aux &aux)
|
||||
(optional reject $&)
|
||||
(optional optional (:var . $$) `(,var nil nil))
|
||||
(optional optional (:var $$ :default _? :svar $$?) `(,var ,(car default) ,(car svar)))
|
||||
(rest reject $&)
|
||||
(rest rest2 (:var . $$) `(,var))
|
||||
(rest2 accept)
|
||||
(rest2 key &key)
|
||||
(rest2 aux &aux)
|
||||
(rest2 reject $&)
|
||||
(key accept)
|
||||
(key other &allow-other-keys t)
|
||||
(key aux &aux)
|
||||
(key reject $&)
|
||||
(key key (:keyword . $k) `(,keyword ,(keyword-to-symbol keyword) nil nil))
|
||||
(key key (:var . $$) `(,var ,var nil nil))
|
||||
(key key (:keyword $$ :default _? :svar $$?) `(,keyword ,(keyword-to-symbol keyword) ,(car default) ,(car svar)))
|
||||
(key key (:var $$ :default _? :svar $$?) `(,var ,var ,(car default) ,(car svar)))
|
||||
(key key ((:keyword $k :var $$) :default _? :svar $$?) `(,keyword ,var ,(car default) ,(car svar)))
|
||||
(other accept)
|
||||
(other aux &aux)
|
||||
(other reject $&)
|
||||
(aux accept)
|
||||
(aux reject $&)
|
||||
(aux aux (:var . $$) `(,var nil))
|
||||
(aux aux (:var $$ :default _?) `(,var ,(car default)))
|
||||
(reject (error "slice-up-lambda-list : ~w ne peut être ici." last-element))))
|
||||
|
||||
;; Exemples :
|
||||
;; (slice-up-lambda-list '(a b &optional u &rest c &key ((:foo bar)) :quux (:baz 'glop) &aux x))
|
||||
;; (slice-up-lambda-list '(a b &rest))
|
||||
;; (slice-up-lambda-list '(a b))
|
||||
|
||||
(declaim (ftype function mini-meval)) ;; récursion mutuelle mini-meval-params / mini-meval
|
||||
(defun mini-meval-params (params global local fixed optional rest key other aux)
|
||||
(if fixed
|
||||
(if (endp params)
|
||||
(error "mini-meval-params : not enough parameters !")
|
||||
(mini-meval-params (cdr params) global (acons (car fixed) (car params) local) (cdr fixed) optional rest key other aux))
|
||||
(if optional
|
||||
(let* ((var (caar optional))
|
||||
(value (if (endp params)
|
||||
(mini-meval (cadar optional) global local)
|
||||
(car params)))
|
||||
(svar (caddar optional))
|
||||
(new-local (acons var value local))
|
||||
(new-local-2 (if svar
|
||||
(acons svar (endp params) new-local)
|
||||
new-local)))
|
||||
(mini-meval-params (cdr params) global new-local-2 nil (cdr optional) rest key other aux))
|
||||
(if rest
|
||||
(mini-meval-params params global (acons (car rest) params local) nil nil nil key other aux)
|
||||
;; TODO : finir d'implémenter &key &allow-other-keys &aux (et relire CLTL).
|
||||
local))))
|
||||
; (if key
|
||||
; (let* ((keyword (first (car key)))
|
||||
; (var (second (car key)))
|
||||
; (maybe-val (member keyword params))
|
||||
; (maybe-val-2 (if maybe-val
|
||||
; (if (n-consp 2 maybe-val)
|
||||
; maybe-val
|
||||
; (error "mini-meval-params : Nombre de paramètres impair alors qu'il y a &key."))))
|
||||
; (svar (fourth (car key)))
|
||||
; (new-local (acons var (if maybe-val-2
|
||||
; (cadr maybe-val-2)
|
||||
; (mini-meval (third (car key)) global local))
|
||||
; local))
|
||||
; (new-local-2 (if svar
|
||||
; (acons svar (not (not (maybe-val-2))) new-local)
|
||||
; new-local)))
|
||||
; (mini-meval-params params global new-local-2 nil nil nil (cdr key) aux)
|
||||
|
||||
(defun mini-meval-get-params-from-real (etat-global etat-local lambda-list effective-parameters)
|
||||
"Lambda-list doit être déjà sliced."
|
||||
(apply #'mini-meval-params effective-parameters etat-global etat-local
|
||||
(cdr (assoc 'fixed lambda-list))
|
||||
(cdr (assoc 'optional lambda-list))
|
||||
(cdr (assoc 'rest lambda-list))
|
||||
(cdr (assoc 'key lambda-list))
|
||||
(cdr (assoc 'other lambda-list))
|
||||
(cdr (assoc 'aux lambda-list))))
|
||||
|
||||
#|
|
||||
Mini-meval est un meval très simple destiné à évaluer les macros et les autres directives avec eval-when :compile-toplevel.
|
||||
|
||||
;; Fonctionnement de mini-meval
|
||||
Mini-meval sera appellé sur des morceaux spécifiques du fichier source. Il faut donc qu'il puisse maintenir son état entre les appels.
|
||||
|#
|
||||
(defun mini-meval (expr &optional etat-global etat-local)
|
||||
#|
|
||||
L'algorithme d'évaluation est très simple et suit le schéma donné dans CLTL 5.1.3 :
|
||||
1) Si l'expression est une forme spéciale, on la traite de manière particulière
|
||||
2) Si l'expression est un appel de macro, on évalue le corps de la macro avec les paramètres tels quels (non évalués),
|
||||
puis on remplace l'appel par son résutlat, et on évalue ce résultat.
|
||||
3) Sinon, c'est un appel de fonction.
|
||||
Pour permettre au code de bas niveau de redéfinir les formes spéciales, on fera d'abord la macro-expansion (étape 2).
|
||||
|#
|
||||
|
||||
(cond-match
|
||||
expr
|
||||
#| 2) Cas des macros |#
|
||||
((:name $ :params _*)
|
||||
(let ((definition (assoc* `(,name macro) #'equal etat-local etat-global)))
|
||||
(if definition
|
||||
#| - Si on a une fonction de ce nom dans l'état-local ou dans l'etat-global, on l'exécute. |#
|
||||
(apply (cdr definition) (mapcar (lambda (x) (mini-meval x etat-global etat-local)) params))
|
||||
(else))))
|
||||
#| 1) Cas des formes spéciales |#
|
||||
((eval-when :situations ($*) :body _*)
|
||||
(if (member :execute situations)
|
||||
(mini-meval body etat-global etat-local)
|
||||
nil))
|
||||
((flet ((:name $ :lambda-list @ :fbody _*)*) :body _*)
|
||||
(mini-meval `(progn ,body)
|
||||
etat-global
|
||||
(reduce* etat-local (lambda (new-etat-local name lambda-list fbody)
|
||||
(acons (cons name 'function)
|
||||
(mini-meval `(lamdba ,lambda-list ,@fbody) etat-global etat-local)
|
||||
new-etat-local))
|
||||
name lambda-list fbody)))
|
||||
((labels ((:name $ :lambda-list @ :fbody _*)*) :body _*)
|
||||
(let* ((new-bindings (reduce* nil (lambda (new-bindings name) `(((,name . 'function) . nil) . ,new-bindings))
|
||||
name))
|
||||
(new-etat-local (append new-bindings etat-local)))
|
||||
(mapcar (lambda (name lambda-list fbody)
|
||||
;; On fait un assoc / setf dans new-bindings, qui ne contient que les fonctions qu'on vient juste d'ajouter, pour éviter
|
||||
;; le risque inexistant de faire une mutation dans etat-local.
|
||||
;; TODO : vérifier que ça marche.
|
||||
(assoc-set `(,name 'function)
|
||||
(mini-meval `(lambda ,lambda-list ,@fbody) new-etat-local)
|
||||
new-bindings
|
||||
#'equal))
|
||||
name lambda-list fbody)
|
||||
(mini-meval `(progn ,body) etat-global new-etat-local)))
|
||||
((let ((:name $ :value _)*) :body _*)
|
||||
(mini-meval `(progn ,body)
|
||||
etat-global
|
||||
(reduce* etat-local (lambda (new-etat-local name value)
|
||||
(acons (cons name 'variable)
|
||||
(mini-meval value etat-global etat-local)
|
||||
new-etat-local))
|
||||
name value)))
|
||||
((let* ((:name $ :value _)*) :body _*)
|
||||
(mini-meval `(progn ,body)
|
||||
etat-global
|
||||
(reduce* etat-local (lambda (new-etat-local name value)
|
||||
(acons (cons name 'variable)
|
||||
;; Comme let sauf new-etat-local au lieu de etat-local ici.
|
||||
(mini-meval value etat-global new-etat-local)
|
||||
new-etat-local))
|
||||
name value)))
|
||||
((macrolet ((:name $ :lambda-list @ :mbody _*)*) :body _*)
|
||||
(mini-meval `(progn ,body)
|
||||
etat-global
|
||||
(reduce* etat-local (lambda (new-etat-local name lambda-list mbody)
|
||||
(acons (cons name 'macro)
|
||||
;; comme le flet sauf nil au lieu de new-etat-local
|
||||
;; CLTL 7.5 :
|
||||
;; The precise rule is that the macro-expansion functions defined
|
||||
;; by macrolet are defined in the global environment; lexically
|
||||
;; scoped entities that would ordinarily be lexically apparent
|
||||
;; are not visible within the expansion functions.
|
||||
(mini-meval `(lambda ,lambda-list ,@mbody) etat-global nil)
|
||||
new-etat-local))
|
||||
name lambda-list mbody)))
|
||||
((progn :body _*)
|
||||
(cdr (last (mapcar (lambda (expr) (mini-meval expr etat-global etat-local))
|
||||
body))))
|
||||
((if :condition _ :si-vrai _ :si-faux _?)
|
||||
(if (mini-meval condition etat-global etat-local)
|
||||
(mini-meval si-vrai etat-global etat-local)
|
||||
(if si-faux
|
||||
(mini-meval (car si-faux) etat-global etat-local)
|
||||
nil)))
|
||||
((lambda :lambda-list @ :body _*)
|
||||
(let ((sliced-lambda-list (slice-up-lambda-list lambda-list)))
|
||||
(lambda (&rest effective-parameters)
|
||||
(mini-meval body
|
||||
etat-global
|
||||
(mini-meval-get-params-from-real etat-global etat-local sliced-lambda-list effective-parameters)))))
|
||||
((defun :name $ :lambda-list @ :body _*)
|
||||
(assoc-set `(,name 'function)
|
||||
(mini-meval `(lambda ,lambda-list ,@body) etat-global etat-local)
|
||||
etat-global
|
||||
#'equal)
|
||||
name)
|
||||
((defmacro :name $ :lambda-list @ :body _*)
|
||||
(assoc-set `(,name 'macro)
|
||||
(mini-meval `(lambda ,lambda-list ,@body) etat-global etat-local)
|
||||
etat-global
|
||||
#'equal)
|
||||
name)
|
||||
((defvar :name $ :value _)
|
||||
(assoc-set `(,name 'variable)
|
||||
(mini-meval value etat-global etat-local)
|
||||
etat-global
|
||||
#'equal)
|
||||
name)
|
||||
((setf/setq)
|
||||
)
|
||||
#| Traitement des appels de fonction |#
|
||||
((:lambda (lambda @ _*) :params _*)
|
||||
#| - Si c'est une fonction anonyme, on l'exécute. |#
|
||||
(apply (mini-meval lambda etat-global etat-local) params))
|
||||
((:name $ :params _*)
|
||||
(let ((definition (assoc* `(,name function) #'equal etat-local etat-global)))
|
||||
(if definition
|
||||
#| - Si on a une fonction de ce nom dans l'état-local ou dans l'etat-global, on l'exécute. |#
|
||||
(apply (cdr definition) (mapcar (lambda (x) (mini-meval x etat-global etat-local)) params))
|
||||
(error "mini-meval : undefined function : ~w" name))))
|
||||
((:name . $$)
|
||||
(let ((definition (assoc* `(,name variable) #'equal etat-local etat-global)))
|
||||
(if definition
|
||||
(cdr definition)
|
||||
(error "mini-meval : undefined variable : ~w" name))))
|
||||
((:num . (? numberp))
|
||||
num)
|
||||
((:str . (? stringp))
|
||||
str)
|
||||
((quote :val _)
|
||||
val)
|
||||
(()
|
||||
nil)))
|
183
match.lisp
183
match.lisp
|
@ -16,6 +16,9 @@
|
|||
;; (a . rest) (and (match a (car expr)) (match rest (cdr expr)))
|
||||
;; () (null expr)
|
||||
;; $ (and (atom expr) (not (null expr)))
|
||||
;; $$ (symbolp expr)
|
||||
;; $k (keywordp expr)
|
||||
;; $& (and (symbolp expr) (member expr '(&optional &rest &key &allow-other-keys &aux)))
|
||||
;; @ liste propre : (and (listp expr) (match @ (cdr expr)))
|
||||
;; @. cons : (consp expr)
|
||||
;; _ t
|
||||
|
@ -39,7 +42,7 @@
|
|||
((eq (car pred) 'function) pred)
|
||||
((eq (car pred) 'lambda) pred)
|
||||
(t
|
||||
`(lambda (x) ,pred))))
|
||||
`(lambda (x) x ,pred))))
|
||||
pattern))
|
||||
|
||||
(defun pattern-match-do-lambdas-1 (pattern)
|
||||
|
@ -50,7 +53,7 @@
|
|||
,(if (second pattern)
|
||||
(let ((?-clause (cdr (third pattern)))
|
||||
(type '_))
|
||||
(when (and (consp ?-clause) (member (car ?-clause) '(nil _ $ @ @.)))
|
||||
(when (and (consp ?-clause) (member (car ?-clause) '(nil _ $ $$ $k $& @ @.)))
|
||||
(setq type (car ?-clause))
|
||||
(setq ?-clause (cdr ?-clause)))
|
||||
;; TODO : (? or foo (? _ and bar baz) (? $ and quux))
|
||||
|
@ -316,7 +319,10 @@
|
|||
(or (when match
|
||||
(let ((match-rest (pattern-match rest (cdr expr))))
|
||||
(when match-rest
|
||||
(append match match-rest)))) ;; TODO : vérifier qu'on n'a pas besoin d'un make-empty-matches en cas de non-match de sous-trucs. (normalement non)
|
||||
;; Attention, les trois lignes suivantes ont été modifiées sans que je comprenne vraiement les manipulations...
|
||||
(append-car-cdr-not-nil
|
||||
(append-captures match (cons (acons-capture capture-name expr (make-empty-matches pattern))
|
||||
match-rest))))))
|
||||
(let ((match-only-rest (pattern-match rest expr)))
|
||||
(when match-only-rest
|
||||
(append (acons-capture capture-name expr (make-empty-matches pattern))
|
||||
|
@ -351,6 +357,18 @@
|
|||
(when (and (atom expr)
|
||||
(not (null expr)))
|
||||
(acons-capture capture-name expr nil)))
|
||||
;; $$
|
||||
((eq '$$ pattern)
|
||||
(when (symbolp expr)
|
||||
(acons-capture capture-name expr nil)))
|
||||
;; $k
|
||||
((eq '$k pattern)
|
||||
(when (keywordp expr)
|
||||
(acons-capture capture-name expr nil)))
|
||||
;; $&
|
||||
((eq '$& pattern)
|
||||
(when (and (symbolp expr) (member expr '(&optional &rest &key &allow-other-keys &aux)))
|
||||
(acons-capture capture-name expr nil)))
|
||||
;; @
|
||||
((eq '@ pattern)
|
||||
(when (propper-list-p expr)
|
||||
|
@ -375,33 +393,49 @@
|
|||
pattern))))
|
||||
|
||||
(defmacro real-match (pattern expr body &optional else-clause)
|
||||
(let* ((result-sym (make-symbol "result"))
|
||||
(pattern-sym (make-symbol "pattern"))
|
||||
(let* ((result-sym (make-symbol "RESULT"))
|
||||
(result-of-if-sym (make-symbol "RESULT-OF-IF"))
|
||||
(pattern-sym (make-symbol "PATTERN"))
|
||||
(else-sym (make-symbol "ELSE"))
|
||||
(pattern-preproc (pattern-match-preprocess-capture
|
||||
(pattern-match-preprocess-multi
|
||||
pattern)))
|
||||
(capture-names (mapcar #'car (make-empty-matches pattern-preproc))))
|
||||
`(let* ((,pattern-sym (pattern-match-do-lambdas ,pattern-preproc))
|
||||
(,result-sym (pattern-match ,pattern-sym ,expr)))
|
||||
;; Filtrage des captures nommées nil.
|
||||
(if ,result-sym
|
||||
,@(if body
|
||||
`((let ,(mapcar (lambda (x)
|
||||
`(,x (cdr (assoc ',x ,result-sym))))
|
||||
capture-names)
|
||||
;; "utilisation" des variables pour éviter les warning unused variable.
|
||||
,@capture-names
|
||||
,@body))
|
||||
(if capture-names
|
||||
`((remove nil ,result-sym :key #'car))
|
||||
`(t)))
|
||||
,else-clause))))
|
||||
(,result-sym (pattern-match ,pattern-sym ,expr))
|
||||
(,result-of-if-sym
|
||||
(if ,result-sym
|
||||
;; Si le match a été effectué avec succès
|
||||
,@(if body
|
||||
;; Si on a un body
|
||||
;; On bind les variables correspondant aux noms de capture
|
||||
`((let ,(mapcar (lambda (x) `(,x (cdr (assoc ',x ,result-sym))))
|
||||
capture-names)
|
||||
;; "utilisation" des variables pour éviter les warning unused variable.
|
||||
,@capture-names
|
||||
;; On définit la fonction "else" qui produit le "code secret" permettant d'exécuter le else.
|
||||
(labels ((else () ',else-sym))
|
||||
;; On exécute le body
|
||||
,@body)))
|
||||
;; S'il n'y a pas de body, on renvoie l'alist des captures s'il y en a ou T sinon.
|
||||
(if capture-names
|
||||
`((remove nil ,result-sym :key #'car))
|
||||
`(t)))
|
||||
;; Si on ne match pas, on renvoie le "code secret" permettant d'exécuter le else.
|
||||
',else-sym)))
|
||||
;; Si le résultat est le "code secret" du else, on fait le else, sinon on renvoie le résultat
|
||||
(if (eq ,result-of-if-sym ',else-sym)
|
||||
,else-clause
|
||||
,result-of-if-sym))))
|
||||
|
||||
(defmacro match (pattern expr &rest body)
|
||||
(if (keywordp pattern)
|
||||
`(real-match (,pattern . ,expr) ,(car body) ,(cdr body))
|
||||
`(real-match ,pattern ,expr ,body)))
|
||||
|
||||
(defmacro if-match (pattern expr body-if body-else)
|
||||
`(real-match ,pattern ,expr (,body-if) ,body-else))
|
||||
|
||||
(defmacro cond-match-1 (expr cond-clauses)
|
||||
(if (endp cond-clauses)
|
||||
'nil
|
||||
|
@ -516,6 +550,117 @@
|
|||
(defun ,add-pattern-function (func)
|
||||
(setf rules (append rules (list func))))))))
|
||||
|
||||
;; Version de match-automaton avec loop :
|
||||
;; `(loop
|
||||
;; with ,state = ',initial-state
|
||||
;; with accumulator = nil
|
||||
;; for step = (cond-match ...)
|
||||
;; when (eq state 'accept)
|
||||
;; return (reverse accumulator)
|
||||
;; when (eq state 'reject)
|
||||
;; return nil
|
||||
;; do (setq state (car step)))
|
||||
|
||||
#|
|
||||
Syntaxe du match-automaton :
|
||||
(match-automaton expression initial-state
|
||||
(stateX stateY [pattern] [code])
|
||||
(stateX stateY [pattern] [code])
|
||||
...)
|
||||
Si pattern n'est pas fourni, alors stateY est soit accept ou reject, pour indiquer qu'on accepte ou rejette l'expression si on arrive sur cet état à la fin.
|
||||
Si pattern est fourni, cela signifie «Si l'élément courant de l'expression matche avec ce pattern, passer à l'élément suivant et aller dans l'état stateY.».
|
||||
Lorsque l'expression est acceptée, pattern-match renvoie une liste associative ((state1 élément-1 ... élément-n) (state2 e1..eN) ...)
|
||||
Les éléments à droite de chaque nom d'état sont obtenus de la manière suivante :
|
||||
Si code est fourni, il est exécuté, et sa valeur de retour est ajoutée à la fin de la liste correspondant à stateX (l'état de départ).
|
||||
Si code n'est pas fourni, rien n'est ajouté à cette liste.
|
||||
Lorsqu'on est sur un certain état, chaque transition partant de cet état est testée dans l'ordre d'écriture, et la première qui matche est choisie.
|
||||
|
||||
De plus, il est possible de spécifier des clauses spéciales, au lieu des (stateX stateY [pattern] [code]) :
|
||||
- (initial code ...), qui exécute le code avant de démarrer (pas très utile...)
|
||||
- (accept code ...), qui exécute le code dans le cas où l'expression est acceptée. La valeur renvoyée par ce code sera la valeur de retour de match-automaton.
|
||||
Dans cette clause, on peut accéder à la valeur qui aurait dû être renvoyée via la variable return-value.
|
||||
- (reject code ...), qui exécute le code dans le cas où l'expression est rejetée. La valeur renvoyée par ce code sera la valeur de retour de match-automaton.
|
||||
Dans cette clause, le dernier élément considéré est stocké dans last-element.
|
||||
Cet élément est celui qui a causé le reject, ou nil si on est au début de la liste, ou nil si on est à la fin de la liste sans accept.
|
||||
;;De même, le dernier état courant est dans last-state.
|
||||
|
||||
Il peut y avoir plusieurs initial, accept et reject, auquel cas tous sont exécutés dans l'ordre d'écriture, et sont concaténés dans un progn,
|
||||
donc seule la valeur de la dernière expression de la dernière clause est renvoyée.
|
||||
|#
|
||||
|
||||
;; ATTENTION, par excès de flemme, match-automaton ne supporte pas la syntax
|
||||
;; (macro :var atom expr code)
|
||||
;; ATTENTION, j'ai renoncé à rendre ce code lisible.
|
||||
(defun match--grouped-transition-to-progns (grouped-transition)
|
||||
;; On remet to, pattern et code bout à bout (c'est tout du code)
|
||||
(mapcar (lambda (x) `(progn ,(car x) ,@(cadr x) ,@(caddr x)))
|
||||
(cdr grouped-transition)))
|
||||
|
||||
(defmacro match-automaton (expr initial-state &rest rules)
|
||||
(match ((:from $$ :to _ :pattern _? :code _*) *) rules
|
||||
(let ((storage (mapcar (lambda (s) (cons s (make-symbol (format nil "STORAGE-~a" (string s))))) (remove-duplicates from)))
|
||||
(expr-sym (make-symbol "EXPR"))
|
||||
(block-sym (make-symbol "BLOCK"))
|
||||
(grouped-transitions (group (mapcar #'list from to pattern code)))
|
||||
(last-state-sym (make-symbol "LAST-STATE"))
|
||||
(last-element-sym (make-symbol "LAST-ELEMENT")))
|
||||
`(let (,@(mapcar (lambda (s) `(,(cdr s) nil)) storage)
|
||||
(,expr-sym ,expr)
|
||||
(,last-state-sym 'initial)
|
||||
(,last-element-sym nil))
|
||||
(block ,block-sym
|
||||
(tagbody
|
||||
initial
|
||||
(progn ,(match--grouped-transition-to-progns (assoc 'initial grouped-transitions)))
|
||||
(go ,initial-state)
|
||||
accept
|
||||
(let ((return-value (list ,@(mapcar (lambda (s) `(cons ',(car s) (reverse ,(cdr s)))) storage))))
|
||||
return-value
|
||||
(return-from ,block-sym
|
||||
(progn return-value
|
||||
,@(match--grouped-transition-to-progns (assoc 'accept grouped-transitions)))))
|
||||
reject
|
||||
(return-from ,block-sym
|
||||
(let ((last-element ,last-element-sym)
|
||||
(last-state ,last-state-sym))
|
||||
last-element
|
||||
last-state
|
||||
(progn nil
|
||||
,@(match--grouped-transition-to-progns (assoc 'reject grouped-transitions)))))
|
||||
;; On va générer ceci :
|
||||
;; state1
|
||||
;; (cond-match ... (go state2) ... (go state1) ...)
|
||||
;; state2
|
||||
;; (cond-match ... (go staten) ... (go reject) ...)
|
||||
;; staten
|
||||
;; (cond-match ... (go accept) ... (go state1) ...)))
|
||||
,@(loop
|
||||
for (from . transitions) in grouped-transitions
|
||||
unless (member from '(initial accept reject))
|
||||
collect from
|
||||
and collect `(setq ,last-state-sym ',from)
|
||||
and collect `(setq ,last-element-sym (car ,expr-sym))
|
||||
and collect `(print ',from)
|
||||
and if (member nil transitions :key #'second)
|
||||
collect `(when (endp ,expr-sym) (print 'auto-accept) (go accept)) ;; TODO : aller à l'état désigné par la dernière transition "finale". + syntaxe (stateX code) => exécute le code à chaque fois qu'on rentre dans stateX.
|
||||
else
|
||||
collect `(when (endp ,expr-sym) (print 'auto-reject) (go reject))
|
||||
end
|
||||
and collect `(cond-match (car ,expr-sym)
|
||||
,@(loop
|
||||
for (to pattern code) in transitions
|
||||
when pattern
|
||||
if code
|
||||
collect `(,@pattern
|
||||
(push (progn ,@code) ,(cdr (assoc from storage)))
|
||||
(setq ,expr-sym (cdr ,expr-sym))
|
||||
(go ,to))
|
||||
else
|
||||
collect `(,@pattern
|
||||
(setq ,expr-sym (cdr ,expr-sym))
|
||||
(go ,to)))
|
||||
(_ (go reject))))))))))
|
||||
|
||||
(load "test-unitaire")
|
||||
(erase-tests match)
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ retourne la liste de leurs valeurs"
|
|||
d’arguments dans un certain environnement."
|
||||
(match (:nil :lclosure :size (? integerp) :rest (? integerp)? :body _*) lclosure
|
||||
(meval lclosure
|
||||
(make-env size args env rest))))
|
||||
(make-env size args env (car rest)))))
|
||||
|
||||
(defun msetf (place val env)
|
||||
(let ((sub-env (get-env-num (first place) env)))
|
||||
|
@ -138,12 +138,12 @@ d’arguments dans un certain environnement."
|
|||
(let ((name (meval macro-name env)))
|
||||
(setf (get name :defmacro) closure)
|
||||
name))
|
||||
((:nil :mcall :func-name (? (get x :defun)) :params _*)
|
||||
((:nil :mcall :func-name (? $$ (get x :defun)) :params _*)
|
||||
(let ((values (meval-args params env)))
|
||||
(meval-lambda (car (get func-name :defun))
|
||||
values
|
||||
(make-env (length values) values env))))
|
||||
((:nil :mcall :macro-name (? (get x :defmacro)) :params _*)
|
||||
((:nil :mcall :macro-name (? $$ (get x :defmacro)) :params _*)
|
||||
(let ((values (meval-args params env)))
|
||||
(meval (lisp2li (meval-lambda (car (get macro-name :defmacro))
|
||||
params
|
||||
|
@ -193,7 +193,7 @@ d’arguments dans un certain environnement."
|
|||
|
||||
(deftest (meval :cvar)
|
||||
(meval '(:cvar 1 2) #(#(() 7 8) 4 5 6))
|
||||
8)
|
||||
5)
|
||||
|
||||
(deftest (meval :call)
|
||||
(meval '(:call + (:const . 3) (:cvar 0 1)) #(() 4 5 6))
|
||||
|
|
|
@ -101,10 +101,10 @@
|
|||
(defun real-run-tests (module-name from)
|
||||
(if (second from)
|
||||
(progn
|
||||
(format t "~&~%-~{ ~a~}~& [Déjà vu]~&" (or module-name '("all-tests")))
|
||||
(format t "~&~%-~{ ~w~}~& [Déjà vu]~&" (or module-name '(all-tests)))
|
||||
t)
|
||||
(progn
|
||||
(format t "~&~%>~{ ~a~}~&" (or module-name '("all-tests")))
|
||||
(format t "~&~%>~{ ~w~}~&" (or module-name '("all-tests")))
|
||||
(setf (second from) t) ;; marquer comme exécuté.
|
||||
(let ((nb-fail (count-if-not #'funcall (reverse (fourth from)))))
|
||||
(if (= nb-fail 0)
|
||||
|
@ -139,14 +139,6 @@
|
|||
(equalp foo #(a b (1 #(2 4 6) 3) c)))
|
||||
t #'booleq)
|
||||
|
||||
(deftest (test-unitaire copy-all)
|
||||
(let* ((foo #(a x (1 #(2 4 7) 5) c))
|
||||
(copy-of-foo (copy-all foo)))
|
||||
copy-of-foo
|
||||
(setf (aref (cadr (aref foo 2)) 1) (cons 'MODIFIED (random 42)))
|
||||
(equalp foo #(a x (1 #(2 4 7) 5) c)))
|
||||
nil #'booleq)
|
||||
|
||||
;;; Exemples d'utilisation.
|
||||
|
||||
;; (erase-tests (a sub-1))
|
||||
|
|
59
util.lisp
59
util.lisp
|
@ -10,10 +10,10 @@
|
|||
;; (loop ......) lire la doc...
|
||||
;; (subst new old tree) remplace old par new dans tree.
|
||||
|
||||
(defmacro aset (k v alist)
|
||||
(defmacro assoc-set (k v alist &optional (compare #'eq))
|
||||
`(let ((my-k ,k)
|
||||
(my-v ,v))
|
||||
(let ((association (assoc my-k ,alist)))
|
||||
(let ((association (assoc my-k ,alist :key ,compare)))
|
||||
(if association
|
||||
(setf (cdr association) my-v)
|
||||
(push (cons my-k my-v) ,alist)))))
|
||||
|
@ -70,11 +70,6 @@
|
|||
collect line
|
||||
finally (close fd)))))
|
||||
|
||||
(defun propper-list-p (l)
|
||||
(or (null l)
|
||||
(and (consp l)
|
||||
(propper-list-p (cdr l)))))
|
||||
|
||||
(defun m-macroexpand-1 (macro)
|
||||
;; TODO : not implemented yet m-macroexpand-1
|
||||
macro ;; Pour éviter le unused variable.
|
||||
|
@ -125,3 +120,53 @@
|
|||
(t
|
||||
(warn "copy-all : Je ne sais pas copier ~w" data)
|
||||
data)))
|
||||
|
||||
(defun flatten (lst &optional rest result)
|
||||
(if (endp lst)
|
||||
(if (endp rest)
|
||||
(reverse result)
|
||||
(flatten (car rest) (cdr rest) result))
|
||||
(if (listp (car lst))
|
||||
(flatten (car lst) (cons (cdr lst) rest) result)
|
||||
(flatten (cdr lst) rest (cons (car lst) result)))))
|
||||
|
||||
(defun mapcar-append (append function &rest lists)
|
||||
(cond ((null lists)
|
||||
append)
|
||||
((member nil lists)
|
||||
append)
|
||||
(t
|
||||
(cons (apply function (mapcar #'car lists))
|
||||
(apply #'mapcar-append append function (mapcar #'cdr lists))))))
|
||||
|
||||
(defun reduce* (initial function &rest lists)
|
||||
(if (or (null lists) (member nil lists))
|
||||
initial
|
||||
(apply #'reduce* (apply function initial (mapcar #'car lists))
|
||||
function
|
||||
(mapcar #'cdr lists))))
|
||||
|
||||
(defun assoc* (item compare &rest alists)
|
||||
(if (not (functionp compare))
|
||||
(apply #'assoc* item #'eq compare alists)
|
||||
(if (endp alists)
|
||||
nil
|
||||
(or (assoc item (car alists) :test compare)
|
||||
(apply #'assoc* item compare (cdr alists))))))
|
||||
|
||||
(defun reverse-alist (alist)
|
||||
(mapcar (lambda (x) (cons (car x) (reverse (cdr x))))
|
||||
alist))
|
||||
|
||||
(defun group-1 (lst &optional result)
|
||||
"Groupe les éléments d'une lste en fonction de leur premier élément, et renvoie une lste associative"
|
||||
(if (endp lst)
|
||||
result
|
||||
(let ((association (assoc (caar lst) result)))
|
||||
(if association
|
||||
(push (cdar lst) (cdr association))
|
||||
(push (cons (caar lst) (list (cdar lst))) result))
|
||||
(group-1 (cdr lst) result))))
|
||||
|
||||
(defun group (lst)
|
||||
(reverse-alist (group-1 lst)))
|
||||
|
|
Loading…
Reference in New Issue
Block a user