Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Décoration de stream et markup language

9 réponses
Avatar
Michael DOUBEZ
Bonjour,

Je faisais une bibliothèque de génération de document type html et je
cherche une solution élégante à un problème. J'ai une solution mais à
titre d'exercice et parce que ça me travaille, j'essaye de trouver une
alternative; je dis cela pour éviter les "'tu n'as qu'à faire
autrement', ' tu n'as qu'à faire au plus simple'" et autres 'tu n'as
qu'à' en général.

Désolé pour le mail un peu long, j'essaye de donner le contexte par clarté.

CADRE:
------

Je cherche à générer un document type html uniquement avec des
modificateurs de flux pour décorer la stream.
ex:
std::cout<<html
<<title<<param.title()
<< ...
<<body
<<h1<<"Titre 1"
<<p<<"First paragraph"<<br<<"on next line";

Tout est résolut à la compilation: std::ostream<<tag -> créé un wrapper
tstream<tag_type,stream_type,etat_type> qui insère ce qu'il faut et gère
un succession d'état de la stream. A chaque changement d'état, une autre
temporaire non nommée est généré.
Ainsi, lorsque je fait
std::cout<<b<<"En gras";

J'ai:
* std::cout<<b écrit "<B" et attends de voir si il y a des attributs
l'etat est 'attr'
* <<"En gras" est des données, donc ">En gras" est écrit
et l'état passe à 'data'.
La succession de temporaires est donc:
std::ostream
-> tstream<b_type,std::ostream,attr>(stream)
-> tstream<b_type,std::ostream,data>(stream)

En bref j'ai plusieurs temporaires qui vivent et meurent sur la ligne.
L'état pourrait être décidé au runtime mais cette façon de faire me
permet de vérifier la correction du document via des traits de tag.

PROBLEME:
---------
Mon problème est la fin du scope:
std::cout<<b<<"En gras";
Va générer: "<B>Engras"

Donc j'utilise la mort du dernier objet pour fermer le tag:
"<B>Engras</B>". Cependant, comme il y a d'autres états avant, ceux-ci
de doivent pas générer de fin de tag sauf si ils sont les derniers ou si
le tag est déjà fermé.
std::cout<<b";
Va générer: "<B />"
std::cout<<b<<"En gras"<<endt(b)<<" pas en gras";
Va générer: "<B>Engras</B> pas en gras"

Donc je suis obligé de passer un token. La solution que j'utilise
actuellement est d'avoir un booléen dans tstream qui me dit si il est le
dernier et celui ci est modifier en cas de close() ou de construction
copie (changement d'état). Puis, dans le destructeur, si le booléen est
vrai, je ferme les tags.

Idéalement, je préfèrerait ne pas avoir d'état runtime, i.e. un
équivalent de:
close(std::cout<<b<<"En gras");
mais en ligne dans la stream:
std::cout<<scoped<<b<<"En gras";

J'ai envisagé:
- les expression template mais ça me parait
beaucoup de complication pour si peu et le
debugage devient impossible
- une locale transmise de bout en bout mais alors
mes tstream doivent avoir un parent commun et
j'ai toujours un état runtime à mettre à jour dans la locale
- demander à fermer la stream par un appel qui ferme
tous les tag mais c'est lourd pour des cas aussi simple que
std::cout<<"Data"<<br<<endt;

Qu'en pensez vous ?

--
Michael

9 réponses

Avatar
Jean-Marc Bourguet
Michael DOUBEZ writes:

Qu'en pensez vous ?



Il faudra le temps d'y repenser, mais j'ai une question. As-tu envisage
l'utilisation des membres xalloc(), iword(), pword() et register_callback()
de ios_base? Si non, c'est peut-etre une piste, si oui, pourquoi n'en
parles-tu pas -- ne fut-ce que pour indiquer que tu l'as rejete et
pourquoi.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Jean-Marc Bourguet
Michael DOUBEZ writes:

Jean-Marc Bourguet a écrit :
> Michael DOUBEZ writes:
>
>> Qu'en pensez vous ?
> Il faudra le temps d'y repenser, mais j'ai une question. As-tu envisage
> l'utilisation des membres xalloc(), iword(), pword() et register_callback()
> de ios_base? Si non, c'est peut-etre une piste, si oui, pourquoi n'en
> parles-tu pas -- ne fut-ce que pour indiquer que tu l'as rejete et
> pourquoi.

Effectivement, je n'ai pas considéré d'utiliser la structure de stockage
des stream. Je pourrais marquer les tags en attente.

Ce que j'aimerais c'est une solution qui à la compilation rende équivalent
d'écrire cout<<b<<"data" ou cout<<"<b>"<<"data"<<<"</b>".
Le RAII du XML en quelque sorte.



Mon probleme c'est que tu as l'air de vouloir:

cout << b; donnant <b/>
et
cout << b << "data"; donnant <b>data</b>

Ca ne me semble possible que de deux manieres:
- en changeant le type de cout << b
- en jouant aussi avec les streambufs.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Michael DOUBEZ
Jean-Marc Bourguet a écrit :
Michael DOUBEZ writes:

Qu'en pensez vous ?



Il faudra le temps d'y repenser, mais j'ai une question. As-tu envisage
l'utilisation des membres xalloc(), iword(), pword() et register_callback()
de ios_base? Si non, c'est peut-etre une piste, si oui, pourquoi n'en
parles-tu pas -- ne fut-ce que pour indiquer que tu l'as rejete et
pourquoi.



Effectivement, je n'ai pas considéré d'utiliser la structure de stockage
des stream. Je pourrais marquer les tags en attente.

Ce que j'aimerais c'est une solution qui à la compilation rende
équivalent d'écrire cout<<b<<"data" ou cout<<"<b>"<<"data"<<<"</b>".
Le RAII du XML en quelque sorte.

Mais, j'ai une autre partie du système qui me permet de reprendre l'état
d'une stream au runtime et ton idée va me simplifier les choses pour une
autre fonctionnalité. Merci.

--
Michael
Avatar
Michael DOUBEZ
Jean-Marc Bourguet a écrit :
Michael DOUBEZ writes:

Jean-Marc Bourguet a écrit :
Michael DOUBEZ writes:

Qu'en pensez vous ?


Il faudra le temps d'y repenser, mais j'ai une question. As-tu envisage
l'utilisation des membres xalloc(), iword(), pword() et register_callback()
de ios_base? Si non, c'est peut-etre une piste, si oui, pourquoi n'en
parles-tu pas -- ne fut-ce que pour indiquer que tu l'as rejete et
pourquoi.


Effectivement, je n'ai pas considéré d'utiliser la structure de stockage
des stream. Je pourrais marquer les tags en attente.

Ce que j'aimerais c'est une solution qui à la compilation rende équivalent
d'écrire cout<<b<<"data" ou cout<<"<b>"<<"data"<<<"</b>".
Le RAII du XML en quelque sorte.



Mon probleme c'est que tu as l'air de vouloir:

cout << b; donnant <b/>
et
cout << b << "data"; donnant <b>data</b>

Ca ne me semble possible que de deux manieres:
- en changeant le type de cout << b



C'est la solution que j'ai choisie. En fait, les tags possèdent des
traits qui sont utilisés dans une machine d'état à la compilation.
Parmi ces traits et policies il y a si le tag:
* peut posséder des attributs et lesquels
* peut avoir des données (données brutes, tags et lesquels)
et autres joyeusetés.

Ainsi si j'essaye de faire:
cout<<html<<h1<<"Titre";
J'aurai une erreur à la compilation car html ne supporte que header,
body, frameset.

- en jouant aussi avec les streambufs.



C'est une solution. Par exemple en utilisant les filtres/inserters de
streambuf de James Kanze. Mais à ce moment là, la vérification des
contraintes se fait au runtime et je perd l'avantage de la décoration
des streams.

--
Michael
Avatar
Marc Boyer
On 2008-12-01, Michael DOUBEZ wrote:
J'ai envisagé:
- les expression template mais ça me parait
beaucoup de complication pour si peu et le
debugage devient impossible
- une locale transmise de bout en bout mais alors
mes tstream doivent avoir un parent commun et
j'ai toujours un état runtime à mettre à jour dans la locale
- demander à fermer la stream par un appel qui ferme
tous les tag mais c'est lourd pour des cas aussi simple que
std::cout<<"Data"<<br<<endt;

Qu'en pensez vous ?



J'en pense que tu as un opérateur associatif a gauche auquel
tu veux donner une sémantique associative à droite, et que
c'est pas facile.

cout<<x<<y vaut operator<<( operator<<(cout,x), y )

alors que tu aimerais

printhtml( x , printhtml( y ) )

ce qui implique de passer par une pile, en jouant
sur les contructeurs/destructeurs.

En plus, tu ne veux pas d'état au runtime, ce qui impose
de gérer la pile à la compilation, mais tu ne veux pas de template,
alors que ça me semble l'outil le plus riche pour résoudre des
trucs compliqués (comme une pile) à la compilation.

Voilà ma maigre contrib (après avoir passé un peu de temps
à tenter de faire une solution template).


Marc Boyer
--
En France, un habitant sur 1000 est en prison.
Aux USA, 7 habitants sur 1000 sont en prison.
Est-ce que les USA sont 7 fois plus sûrs ?
Avatar
James Kanze
On Dec 1, 12:10 pm, Michael DOUBEZ wrote:
Jean-Marc Bourguet a écrit :
> Michael DOUBEZ writes:
>> Jean-Marc Bourguet a écrit :
>>> Michael DOUBEZ writes:



>>>> Qu'en pensez vous ?
>>> Il faudra le temps d'y repenser, mais j'ai une question.
>>> As-tu envisage l'utilisation des membres xalloc(),
>>> iword(), pword() et register_callback() de ios_base? Si
>>> non, c'est peut-etre une piste, si oui, pourquoi n'en
>>> parles-tu pas -- ne fut-ce que pour indiquer que tu l'as
>>> rejete et pourquoi.
>> Effectivement, je n'ai pas considéré d'utiliser la
>> structure de stockage des stream. Je pourrais marquer les
>> tags en attente.



>> Ce que j'aimerais c'est une solution qui à la compilation
>> rende équivalent d'écrire cout<<b<<"data" ou
>> cout<<"<b>"<<"data"<<<"</b>". Le RAII du XML en quelque
>> sorte.



> Mon probleme c'est que tu as l'air de vouloir:



> cout << b; donnant <b/>
> et
> cout << b << "data"; donnant <b>data</b>



> Ca ne me semble possible que de deux manieres:
> - en changeant le type de cout << b



C'est la solution que j'ai choisie. En fait, les tags
possèdent des traits qui sont utilisés dans une machine d'état
à la compilation. Parmi ces traits et policies il y a si le
tag:
* peut posséder des attributs et lesquels
* peut avoir des données (données brutes, tags et lesquels)
et autres joyeusetés.



Ainsi si j'essaye de faire:
cout<<html<<h1<<"Titre";
J'aurai une erreur à la compilation car html ne supporte que
header, body, frameset.



> - en jouant aussi avec les streambufs.



C'est une solution. Par exemple en utilisant les
filtres/inserters de streambuf de James Kanze. Mais à ce
moment là, la vérification des contraintes se fait au runtime
et je perd l'avantage de la décoration des streams.



L'un n'exclut pas l'autre ; les types pourraient être des
espèces d'OutputStreamWrapper, qui insèrent et enlève le
streambuf filtrant.

Méfie-toi quand même de l'ordre de destruction. C'est l'inverse
de l'ordre de construction, non de l'ordre d'invocation des <<.
Si les temporaires qui t'intéressent apparaissent tous comme
valeurs de retour des <<, il ne doit pas y avoir des problèmes,
mais sinon... quand j'ai implémenté les StateSavingManip, il
fallait que je me serve de l'enrégistrement dynamique lors de
l'exécution de l'opérateur<<.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Avatar
Michael DOUBEZ
Marc Boyer a écrit :
On 2008-12-01, Michael DOUBEZ wrote:
J'ai envisagé:
- les expression template mais ça me parait
beaucoup de complication pour si peu et le
debugage devient impossible
- une locale transmise de bout en bout mais alors
mes tstream doivent avoir un parent commun et
j'ai toujours un état runtime à mettre à jour dans la locale
- demander à fermer la stream par un appel qui ferme
tous les tag mais c'est lourd pour des cas aussi simple que
std::cout<<"Data"<<br<<endt;

Qu'en pensez vous ?



J'en pense que tu as un opérateur associatif a gauche auquel
tu veux donner une sémantique associative à droite, et que
c'est pas facile.

cout<<x<<y vaut operator<<( operator<<(cout,x), y )

alors que tu aimerais

printhtml( x , printhtml( y ) )




Pas exactement, je suis content avec l'associativité à droite puisque je
décore de gauche à droite html<<head<<body ....
Mais ça résume très bien le problème pour scope: ce serait effectivement
une associativité à gauche:
cout<<scoped<<html -> scoped(cout)(html<<...)

ce qui implique de passer par une pile, en jouant
sur les contructeurs/destructeurs.

En plus, tu ne veux pas d'état au runtime, ce qui impose
de gérer la pile à la compilation, mais tu ne veux pas de template,
alors que ça me semble l'outil le plus riche pour résoudre des
trucs compliqués (comme une pile) à la compilation.



Je suis d'accord avec toi, les expression template sont idéales pour
inverser l'ordre puisque je pourrai avoir une évaluation différée du
membre à droite de scoped.

Dans ce cas, je passerais tout en expression template plutôt que mon
système actuel d'état de stream. C'est juste que les ET ont tendance à
générer des ligne de debug assez conséquentes.

Voilà ma maigre contrib (après avoir passé un peu de temps
à tenter de faire une solution template).



Merci. En écrivant ma réponse, je me demande si mon argument du debugage
n'est pas un peu faible.

En fait, si j'utilise des ET, je me pose maintenant des questions sur
l'exécution des fonctions en ligne:
...<<"Result:"<<obj.method()<<...
Je ne suis pas sûr que ce soit si trivial et intuitif de différer
l'écriture des données dans la stream.
A méditer.

--
Michael
Avatar
Michael DOUBEZ
James Kanze a écrit :
On Dec 1, 12:10 pm, Michael DOUBEZ wrote:
Jean-Marc Bourguet a écrit :
Michael DOUBEZ writes:
Jean-Marc Bourguet a écrit :
Michael DOUBEZ writes:









Qu'en pensez vous ?


Il faudra le temps d'y repenser, mais j'ai une question.
As-tu envisage l'utilisation des membres xalloc(),
iword(), pword() et register_callback() de ios_base? Si
non, c'est peut-etre une piste, si oui, pourquoi n'en
parles-tu pas -- ne fut-ce que pour indiquer que tu l'as
rejete et pourquoi.


Effectivement, je n'ai pas considéré d'utiliser la
structure de stockage des stream. Je pourrais marquer les
tags en attente.







Ce que j'aimerais c'est une solution qui à la compilation
rende équivalent d'écrire cout<<b<<"data" ou
cout<<"<b>"<<"data"<<<"</b>". Le RAII du XML en quelque
sorte.







Mon probleme c'est que tu as l'air de vouloir:





cout << b; donnant <b/>
et
cout << b << "data"; donnant <b>data</b>





Ca ne me semble possible que de deux manieres:
- en changeant le type de cout << b





C'est la solution que j'ai choisie. En fait, les tags
possèdent des traits qui sont utilisés dans une machine d'état
à la compilation. Parmi ces traits et policies il y a si le
tag:
* peut posséder des attributs et lesquels
* peut avoir des données (données brutes, tags et lesquels)
et autres joyeusetés.



Ainsi si j'essaye de faire:
cout<<html<<h1<<"Titre";
J'aurai une erreur à la compilation car html ne supporte que
header, body, frameset.



- en jouant aussi avec les streambufs.





C'est une solution. Par exemple en utilisant les
filtres/inserters de streambuf de James Kanze. Mais à ce
moment là, la vérification des contraintes se fait au runtime
et je perd l'avantage de la décoration des streams.



L'un n'exclut pas l'autre ; les types pourraient être des
espèces d'OutputStreamWrapper, qui insèrent et enlève le
streambuf filtrant.



Je vais regarder de plus près mais j'avoue ne pas trop aimer de prime
abord l'indirection de mettre et enlever des streambuf.

J'aurais justement aimé des opérations sans effet de bord: uniquement
des insertion dans la stream et des retours de temporaires non nommées
pour gérer les états.

Méfie-toi quand même de l'ordre de destruction. C'est l'inverse
de l'ordre de construction, non de l'ordre d'invocation des <<.
Si les temporaires qui t'intéressent apparaissent tous comme
valeurs de retour des <<, il ne doit pas y avoir des problèmes,
mais sinon...



Effectivement. Donc, quoi qu'il se passe :
* soit je trouve un moyen de modifier l'état des objets précédemment
créés si j'exécute quelque chose dans le destructeur. C'est ce que je
fait actuellement:

Aujourd'hui, j'ai trois constructeur à ma tstream (qui ressemble en
fonctionalités au OutputStreamWrapper):
//le constructeur originel - à partir d'une ostream
tstream(ostream &):guard(true)...

//le constructeur par copy pour passer le token de destruction:
tstream(const tstream& t):guard(true)...{t.guardúlse;}

//le constructeur de changement d'état
template<PREVIOUS_STATE>
tstream(const tstream<PREVIOUS_STATE>& t):guard(true)...{t.guardúlse;}

Et mon destructeur test (mutable bool)guard pour savoir si il va
invoquer la procédure de fin de scope.

* soit je passe une seule temporaire de bout en bout et à ce moment,
mon système d'état à la compilation n'est plus applicable.

En fait, il me manque un look-ahead pour savoir comment mon tag va se
fermer; ce que ne permet pas l'associativité de << comme l'a fait
remarqué Marc Boyer.

--
Michael
Avatar
Marc Boyer
On 2008-12-01, Michael DOUBEZ wrote:
Marc Boyer a écrit :
On 2008-12-01, Michael DOUBEZ wrote:
J'ai envisagé:
- les expression template mais ça me parait
beaucoup de complication pour si peu et le
debugage devient impossible
- une locale transmise de bout en bout mais alors
mes tstream doivent avoir un parent commun et
j'ai toujours un état runtime à mettre à jour dans la locale
- demander à fermer la stream par un appel qui ferme
tous les tag mais c'est lourd pour des cas aussi simple que
std::cout<<"Data"<<br<<endt;

Qu'en pensez vous ?



J'en pense que tu as un opérateur associatif a gauche auquel
tu veux donner une sémantique associative à droite, et que
c'est pas facile.

cout<<x<<y vaut operator<<( operator<<(cout,x), y )

alors que tu aimerais

printhtml( x , printhtml( y ) )




Pas exactement, je suis content avec l'associativité à droite puisque je
décore de gauche à droite html<<head<<body ....
Mais ça résume très bien le problème pour scope: ce serait effectivement
une associativité à gauche:
cout<<scoped<<html -> scoped(cout)(html<<...)



J'ai bidouillé deux trois trucs, et je n'arrive pas à grand chose.
Comme de toute façon, l'opérateur est associatif à gauche, je ne vois
pas comment détecter structurellement la fin de l'expression, c-à-d
comment par exemple
cout<<scoped<<html<<body
peut savoir qu'il est le dernier (la présence du ; après), ou qu'il
y a quelque chose après (un <<b par exemple).
La seule chose que j'envisage, mais c'est dynamique, c'est
de stocker tout ce qu'il faut fermer dans le "scoped" et d'écrire
lors de la destruction du scoped, mais je ne suis pas assez sur de
moi sur les règles de destructions.

ce qui implique de passer par une pile, en jouant
sur les contructeurs/destructeurs.

En plus, tu ne veux pas d'état au runtime, ce qui impose
de gérer la pile à la compilation, mais tu ne veux pas de template,
alors que ça me semble l'outil le plus riche pour résoudre des
trucs compliqués (comme une pile) à la compilation.



Je suis d'accord avec toi, les expression template sont idéales pour
inverser l'ordre puisque je pourrai avoir une évaluation différée du
membre à droite de scoped.

Dans ce cas, je passerais tout en expression template plutôt que mon
système actuel d'état de stream. C'est juste que les ET ont tendance à
générer des ligne de debug assez conséquentes.



Tout à fait. On demande au compilateur de faire un travail jusqu'alors
réservé à l'exécution. Et depuis que l'info existe, on a développé pleins
de techniques et outils pour débuger du code exécuté. Mais je n'ai
pas encore vu de débugeur d'ET.

Voilà ma maigre contrib (après avoir passé un peu de temps
à tenter de faire une solution template).



Merci. En écrivant ma réponse, je me demande si mon argument du debugage
n'est pas un peu faible.



Mon questionnement serait: quel est le gain attendu par la
gestion des templates ? Est-ce que ça vaut le coup ?

Et puis je n'arrive pas à voir non plus comment les templates
peuvent "voir" la fin de l'expression. Autant en exécution dynamique,
on peut se reposer sur le destructeur de "scope", autant en ET,
je ne vois pas comment se passer d'un marqueur de fin.
std::cout<<scope<<"Data"<<br<<ends;

Je ne suis pas sûr que ce soit si trivial et intuitif de différer
l'écriture des données dans la stream.



Ne suffit-il pas de tout stoquer par exemple dans une stringstream ?

Marc Boyer
--
En France, un habitant sur 1000 est en prison.
Aux USA, 7 habitants sur 1000 sont en prison.
Est-ce que les USA sont 7 fois plus sûrs ?