Commentaires dans CPS, à étoffer un peu. Et léger typofix.
This commit is contained in:
parent
890a1067fc
commit
ef07bc5a28
2 changed files with 116 additions and 59 deletions
|
@ -1,10 +1,23 @@
|
||||||
#!/bin/bash /usr/scripts/python.sh
|
#!/bin/bash /usr/scripts/python.sh
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) Valentin Samir
|
||||||
|
# Licence : GPLv3
|
||||||
u"""
|
u"""
|
||||||
Copyright (C) Valentin Samir
|
Module implémentant la "Continuation Passing Style".
|
||||||
Licence : GPLv3
|
|
||||||
|
|
||||||
|
Le concept est d'exécuter des fonctions qui sont des TailCallers,
|
||||||
|
qui elles-mêmes seront amenées à appeler d'autres fonctions,
|
||||||
|
qui sont des TailCalls, ou qui lèvent des exceptions de type
|
||||||
|
Continue(TailCall).
|
||||||
|
|
||||||
|
Ces TailCalls pourraient être amenés à exécuter d'autres fonctions,
|
||||||
|
dans ce cas, pour faire en sorte que leur empreinte mémoire soit
|
||||||
|
faible, auquel cas, soit ces fonctions sont elles-mêmes des
|
||||||
|
TailCalls, soit ce sont des TailCallers.
|
||||||
|
|
||||||
|
L'intérêt de ce concept est de favoriser une stack à empreinte
|
||||||
|
basse.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -16,27 +29,33 @@ import traceback
|
||||||
|
|
||||||
if '/usr/scripts' not in sys.path:
|
if '/usr/scripts' not in sys.path:
|
||||||
sys.path.append('/usr/scripts')
|
sys.path.append('/usr/scripts')
|
||||||
|
|
||||||
from pythondialog import Dialog as PythonDialog
|
from pythondialog import Dialog as PythonDialog
|
||||||
from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess
|
from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess
|
||||||
from pythondialog import error as DialogError
|
from pythondialog import error as DialogError
|
||||||
from gestion.affich_tools import get_screen_size, coul
|
from gestion import affichage
|
||||||
|
|
||||||
debug_enable = False
|
from cranslib import clogger
|
||||||
debugf = None
|
|
||||||
|
# Modifier level à debug pour débugguer.
|
||||||
|
LOGGER = clogger.CLogger('gest_crans_lc', service="CPS", level='info')
|
||||||
|
ENCODING = affichage.guess_preferred_encoding()
|
||||||
|
|
||||||
def mydebug(txt):
|
def mydebug(txt):
|
||||||
"""Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log"""
|
"""Petite fonction pour logguer du debug avec CLogger.
|
||||||
global debugf, debug_enable
|
LOGGER.debug n'écrit que si level est à debug"""
|
||||||
if debug_enable:
|
|
||||||
if debugf is None:
|
|
||||||
debugf = open('/tmp/gest_crans_lc.log', 'w')
|
|
||||||
if isinstance(txt, list):
|
if isinstance(txt, list):
|
||||||
for v in txt:
|
txt = "\n".join(txt)
|
||||||
mydebug(' ' + str(v))
|
if isinstance(txt, unicode):
|
||||||
else:
|
txt = txt.encode(ENCODING)
|
||||||
debugf.write(str(txt)+'\n')
|
LOGGER.debug(txt)
|
||||||
debugf.flush()
|
|
||||||
os.fsync(debugf)
|
class Continue(Exception):
|
||||||
|
"""Exception pour envoyer des TailCall en les raisant
|
||||||
|
l'argument tailCall est soit une fonction décorée, soit
|
||||||
|
la fonction retournante elle-même."""
|
||||||
|
def __init__(self, tailCall):
|
||||||
|
self.tailCall = tailCall
|
||||||
|
|
||||||
# Implémentation "à la main" de la tail récursion en python
|
# Implémentation "à la main" de la tail récursion en python
|
||||||
# voir http://kylem.net/programming/tailcall.html
|
# voir http://kylem.net/programming/tailcall.html
|
||||||
|
@ -45,63 +64,91 @@ def mydebug(txt):
|
||||||
# code en CPS (Continuation Passing Style)
|
# code en CPS (Continuation Passing Style)
|
||||||
class TailCaller(object) :
|
class TailCaller(object) :
|
||||||
"""
|
"""
|
||||||
Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion
|
Un TailCaller est le doux nom d'un objet (une fonction décorée) qui crée et
|
||||||
faite "à la main" en python (voir http://kylem.net/programming/tailcall.html)
|
joue avec des TailCall. Les TailCalls sont des jouets qui servent à réduire
|
||||||
|
l'empreinte dans la stack d'appels récursifs.
|
||||||
|
|
||||||
Parameters
|
Lorsqu'on __call__ un TailCaller, celui-ci exécute la fonction qu'il décore.
|
||||||
----------
|
Si le retour de celle-ci est un TailCall, on exécute le call de celui-ci,
|
||||||
f : function
|
et ainsi de suite, jusqu'à avoir un retour qui ne soit pas un TailCall.
|
||||||
Fonction décoré
|
|
||||||
|
Un call ou la fonction du TailCaller peuvent retourner des erreurs de type
|
||||||
|
continue, qui généralement contiennent un TailCall, mais peuvent aussi
|
||||||
|
contenir une sortie.
|
||||||
"""
|
"""
|
||||||
other_callers = {}
|
other_callers = {}
|
||||||
def __init__(self, f) :
|
|
||||||
|
def __init__(self, f):
|
||||||
|
"""On prend f et son nom et on les stocke dans le TailCaller, wouhou,
|
||||||
|
c'est trop fou."""
|
||||||
self.f = f
|
self.f = f
|
||||||
self.func_name = f.func_name
|
self.func_name = f.func_name
|
||||||
TailCaller.other_callers[id(self)]=f.func_name
|
TailCaller.other_callers[id(self)]=f.func_name
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
"""Supprime le TailCaller de la liste"""
|
||||||
del(TailCaller.other_callers[id(self)])
|
del(TailCaller.other_callers[id(self)])
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs) :
|
def __call__(self, *args, **kwargs):
|
||||||
|
"""Quand on appelle le TailCaller pour de vrai,
|
||||||
|
on vérifie la position actuelle du bordel dans la stack,
|
||||||
|
et on exécute la fonction référencée, et les TailCalls qu'on
|
||||||
|
peut croiser sur la route, pour peu que leur position en
|
||||||
|
stack soit postérieure à celle du TailCaller actuel."""
|
||||||
mydebug("***%s calling" % self.func_name)
|
mydebug("***%s calling" % self.func_name)
|
||||||
stacklvl = len(inspect.stack())
|
stacklvl = len(inspect.stack())
|
||||||
|
|
||||||
|
# On applique f une première fois aux arguments
|
||||||
|
# qui vont bien.
|
||||||
|
# Cela peut retourner un TailCall, un résultat, ou
|
||||||
|
# lever une erreur de type Continue. Souvent les fonctions
|
||||||
|
# raisent des Continue contenant des TailCall, dans le but
|
||||||
|
# de ne pas augmenter le contenu de la stack.
|
||||||
try:
|
try:
|
||||||
ret = self.f(*args, **kwargs)
|
ret = self.f(*args, **kwargs)
|
||||||
except Continue as c:
|
except Continue as c:
|
||||||
|
# Le TailCall est là-dedans.
|
||||||
ret = c.tailCall
|
ret = c.tailCall
|
||||||
|
|
||||||
|
# Si f a terminé, ret n'est pas un TailCall mais sa valeur
|
||||||
|
# de retour. Sinon, on entre dans la boucle.
|
||||||
while isinstance(ret, TailCall):
|
while isinstance(ret, TailCall):
|
||||||
# Si la continuation a été créer par un TailCaller précédent, on propage
|
# Ici, on regarde l'endroit dans la stack de l'objet courant
|
||||||
# Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall
|
# vis-à-vis de la position dans la stack de ret. (plus le stack
|
||||||
# Crée après lui.
|
# lvl est élevé, plus on a été appelé récemment)
|
||||||
if stacklvl>=ret.stacklvl:
|
# On constate donc ici qu'on ne s'intéresse qu'aux TailCall qui
|
||||||
|
# ont été créés après l'appel du TailCaller actuel.
|
||||||
|
if stacklvl >= ret.stacklvl:
|
||||||
mydebug("***%s terminate" % self.func_name)
|
mydebug("***%s terminate" % self.func_name)
|
||||||
|
# Ici, on peut donc raise un Continue, qui fait sortir
|
||||||
|
# de l'exécution du TailCaller courant.
|
||||||
raise Continue(ret)
|
raise Continue(ret)
|
||||||
mydebug("***%s doing %s" % (self.func_name, ret))
|
mydebug("***%s doing %s" % (self.func_name, ret))
|
||||||
|
|
||||||
|
# L'appel à un TailCall se fait via la méthode handle.
|
||||||
|
# Si ça retourne une continuation, on récupère le tailCall
|
||||||
|
# dedans dans ret, et on boucle. Sinon, soit le TailCall
|
||||||
|
# retourne un TailCall, soit il retourne un résultat.
|
||||||
try:
|
try:
|
||||||
ret = ret.handle()
|
ret = ret.handle()
|
||||||
except Continue as c:
|
except Continue as c:
|
||||||
ret = c.tailCall
|
ret = c.tailCall
|
||||||
|
|
||||||
mydebug("***%s returning %s" % (self.func_name, ret))
|
mydebug("***%s returning %s" % (self.func_name, ret))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def tailcaller(f):
|
def tailcaller(f):
|
||||||
"""
|
"""Décorateur permettant aux fonctions d'une classe d'être
|
||||||
Décorateur retardant la décoration par TailCaller d'une fonction
|
décorées comme TailCaller à l'instanciation de ladite classe.
|
||||||
À utiliser sur les fonctions faisant de la tail récursion
|
|
||||||
et devant retourner une valeur. On l'utilise de toute
|
Cela permet que seules les instances aient des méthodes qui
|
||||||
façon sur la fonction d'entrée dans l'application
|
soient des TailCallers. La conversion se faisant au premier
|
||||||
Une fonction de devrait pas instancier de TailCall sans
|
appel de la fonction dans l'instance.
|
||||||
être décoré par ce décorteur de façon générale
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
f.tailCaller = True
|
f.tailCaller = True
|
||||||
return f
|
return f
|
||||||
|
|
||||||
class Continue(Exception):
|
|
||||||
"""Exception pour envoyer des TailCall en les raisant"""
|
|
||||||
def __init__(self, tailCall):
|
|
||||||
self.tailCall = tailCall
|
|
||||||
|
|
||||||
class TailCall(object) :
|
class TailCall(object) :
|
||||||
"""
|
"""
|
||||||
Appel tail récursif à une fonction et ses argument
|
Appel tail récursif à une fonction et ses argument
|
||||||
|
@ -109,12 +156,17 @@ class TailCall(object) :
|
||||||
l'appel à une fonction (avec ses listes d'arguements)
|
l'appel à une fonction (avec ses listes d'arguements)
|
||||||
à un moment futur. La fonction sera appelée dans les
|
à un moment futur. La fonction sera appelée dans les
|
||||||
cas suivant :
|
cas suivant :
|
||||||
* Le TailCall est retourné par une fonction qui est un TailCaller
|
* Le TailCall est retourné dans l'appel de la fonction d'un
|
||||||
|
TailCaller.
|
||||||
* L'exception Continue(TailCall(...)) est levée et traverse
|
* L'exception Continue(TailCall(...)) est levée et traverse
|
||||||
une fonction qui est un TailCaller
|
une fonction qui est un TailCaller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, call, *args, **kwargs) :
|
def __init__(self, call, *args, **kwargs) :
|
||||||
|
"""Création du TailCall, on note la position dans la pile
|
||||||
|
Si la fonction passée à TailCall est un TailCall, on override
|
||||||
|
le TailCall qu'on est en train d'instancier.
|
||||||
|
"""
|
||||||
self.stacklvl = len(inspect.stack())
|
self.stacklvl = len(inspect.stack())
|
||||||
if isinstance(call, TailCall):
|
if isinstance(call, TailCall):
|
||||||
call.kwargs.update(**kwargs)
|
call.kwargs.update(**kwargs)
|
||||||
|
@ -129,6 +181,8 @@ class TailCall(object) :
|
||||||
self.check(self.args, self.kwargs)
|
self.check(self.args, self.kwargs)
|
||||||
|
|
||||||
def check(self, args, kwargs):
|
def check(self, args, kwargs):
|
||||||
|
"""On vérifie si le TailCall a le bon nombre d'arguments,
|
||||||
|
et si les keywords arguments sont bons."""
|
||||||
call = self.call
|
call = self.call
|
||||||
if isinstance(call, TailCaller):
|
if isinstance(call, TailCaller):
|
||||||
call = call.f
|
call = call.f
|
||||||
|
@ -136,6 +190,7 @@ class TailCall(object) :
|
||||||
if targs.varargs is not None:
|
if targs.varargs is not None:
|
||||||
if len(args) + len(kwargs) > len(targs.args):
|
if len(args) + len(kwargs) > len(targs.args):
|
||||||
raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs)))
|
raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs)))
|
||||||
|
|
||||||
for key in kwargs:
|
for key in kwargs:
|
||||||
if key not in targs.args:
|
if key not in targs.args:
|
||||||
raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key))
|
raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key))
|
||||||
|
@ -160,7 +215,11 @@ class TailCall(object) :
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
tmpkwargs={}
|
"""Met à jour le TailCall en ajoutant args et kwargs
|
||||||
|
à la liste des arguments à passer au call lors de son appel
|
||||||
|
via handle.
|
||||||
|
"""
|
||||||
|
tmpkwargs = {}
|
||||||
tmpkwargs.update(self.kwargs)
|
tmpkwargs.update(self.kwargs)
|
||||||
tmpkwargs.update(kwargs)
|
tmpkwargs.update(kwargs)
|
||||||
self.check(self.args + args, tmpkwargs)
|
self.check(self.args + args, tmpkwargs)
|
||||||
|
@ -171,14 +230,12 @@ class TailCall(object) :
|
||||||
def handle(self) :
|
def handle(self) :
|
||||||
"""
|
"""
|
||||||
Exécute la fonction call sur sa liste d'argument.
|
Exécute la fonction call sur sa liste d'argument.
|
||||||
on déréférence les TailCaller le plus possible pour réduire
|
|
||||||
la taille de la stack
|
Si la fonction à exécuter est un TailCaller, on déréférence.
|
||||||
"""
|
"""
|
||||||
caller = None
|
|
||||||
call = self.call
|
call = self.call
|
||||||
while isinstance(call, TailCaller) :
|
while isinstance(call, TailCaller):
|
||||||
caller = call
|
call = call.f
|
||||||
call = self.call.f
|
|
||||||
return call(*self.args, **self.kwargs)
|
return call(*self.args, **self.kwargs)
|
||||||
|
|
||||||
def unicode_of_Error(x):
|
def unicode_of_Error(x):
|
||||||
|
@ -227,7 +284,7 @@ class Dialog(object):
|
||||||
"""
|
"""
|
||||||
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
|
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
|
||||||
"""
|
"""
|
||||||
(lines, cols) = get_screen_size()
|
(lines, cols) = affichage.getTerminalSize()
|
||||||
print "\033[48;5;17m"
|
print "\033[48;5;17m"
|
||||||
print " "*(lines * cols)
|
print " "*(lines * cols)
|
||||||
cols = int(min(cols/2, 65))
|
cols = int(min(cols/2, 65))
|
||||||
|
|
|
@ -723,19 +723,19 @@ class Dialog(proprio.Dialog):
|
||||||
def set_chambre(adherent, chbre):
|
def set_chambre(adherent, chbre):
|
||||||
try:
|
try:
|
||||||
adherent['postalAddress'] = []
|
adherent['postalAddress'] = []
|
||||||
adherent['chbre'] = unicode(output, 'utf-8')
|
adherent['chbre'] = unicode(chbre, 'utf-8')
|
||||||
except UniquenessError:
|
except UniquenessError:
|
||||||
if expulse_squatteur(adherent, chbre):
|
if expulse_squatteur(adherent, chbre):
|
||||||
# La chambre est maintenant normalement libre
|
# La chambre est maintenant normalement libre
|
||||||
adherent['chbre'] = unicode(output, 'utf-8')
|
adherent['chbre'] = unicode(chbre, 'utf-8')
|
||||||
else:
|
else:
|
||||||
raise Continue(self_cont)
|
raise Continue(self_cont)
|
||||||
return adherent
|
return adherent
|
||||||
|
|
||||||
def todo(chbre, adherent, self_cont, success_cont):
|
def todo(chbre, adherent, self_cont, success_cont):
|
||||||
if not output:
|
if not chbre:
|
||||||
raise Continue(self_cont)
|
raise Continue(self_cont)
|
||||||
if output == "????":
|
if chbre == "????":
|
||||||
raise ValueError("Chambre ???? invalide")
|
raise ValueError("Chambre ???? invalide")
|
||||||
if create:
|
if create:
|
||||||
return set_chambre(adherent, chbre)
|
return set_chambre(adherent, chbre)
|
||||||
|
@ -745,7 +745,7 @@ class Dialog(proprio.Dialog):
|
||||||
adherent.validate_changes()
|
adherent.validate_changes()
|
||||||
adherent.history_gen()
|
adherent.history_gen()
|
||||||
adherent.save()
|
adherent.save()
|
||||||
self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output)
|
self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % chbre)
|
||||||
raise Continue(success_cont(adherent=adherent))
|
raise Continue(success_cont(adherent=adherent))
|
||||||
|
|
||||||
(code, output) = self.handle_dialog(cont, box)
|
(code, output) = self.handle_dialog(cont, box)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue