Bonjour,
Actuellement je fait un préprocesseur C++ en Ruby.
L'intéret ? C'est beaucoup plus simple et pratique à utiliser que le préprocesseur C++ normal, et puis Ruby est vraiment un langage très agréable pour faire des DSL. Si vous avez le temps, ce serait sympa de le tester et de me dire ce que vous en pensez, des fonctionnalités que vous aimeriez avoir, etc...
Ci dessous, quelques exemples pour illustrer l'intéret de la bête.
Quelques notes préliminaires :
Vous pouvez insérer du code Ruby à n'importe quel endroit du fichier avec les conventions suivantes :
<% code %> : Le code Ruby est interprété.
<%= code %> : Le code Ruby est interprété et son résultat est inséré en place.
% code : Si le premier caractère de la ligne est %, toute la ligne sera interprétée en Ruby.
Si vous avez un répertoire rppconf à la racine de votre projet, vous pouvez y insérer des fichiers en langage YAML (un langage simplissime et esthétique pour sérialiser des données). Les données du fichier seront ensuite accessibles n'importe où dans votre source dans la variable <nom du fichier yaml>.!
Si vous avez un fichier rpphelpers.rb à la racine de votre projet, vous pouvez aussi y insérer des helpers (fonctions pratiques) qui vous permettent de garder un code Ruby propre et clair dans vos sources.
En Ruby, on dispose de "symboles", ce sont grossièrement l'équivalent de chaines de caractères constantes. Un symbole se préfixe par deux points ":". Par rapport à des chaines de caractères, l'intéret c'est que les symboles sont des singletons.
Exemple 1:
Créer un fichier nommé "project.yaml" dans le répertoire "rppconf", et mettre ceci dedans :
YAML est un langage qui fonctionne principalement sur le principe clef : valeur. Dans cet exemple nos clefs sont des symboles. On peut créer des listes en les préfixant par "-".
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 --- :name: Ruby pre-processor example :authors: - NewbiZ :languages: - C++ - Ruby :website: http://www.speednova.cc :usage: Available arguments are listed below :arguments: - :char: h :text: Display RCPP's help. - :char: v :text: Display RCPP's version number. - :char: d :text: Deprecated argument. :version: 1.2
On dispose donc maintenant d'une variable "project" accessible partout dans nos sources, et contenant ces valeurs.
Créons donc un fichier example1.rpp (notez l'extension) pour tester :
Après avoir executé ./rppgen.rb, vous obtiendrez le fichier example1.cpp suivant :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 %# Helper function displaying text while generating files (see rpphelpers.rb) % echo "Pre-processing \"example1.cpp\"" #include <cstdlib> #include <iostream> int main (int argc, char const* argv[]) { std::cout << "This is " << "<%= project[:name] %>" << " version " << "<%= project[:version] %>" << " build date: " << "<%= Time.now.to_s %>" << std::endl << std::endl; std::cout << "Visit us at " << "<%= project[:website] %>" << std::endl << std::endl; std::cout << "<%= project[:usage] %>" << std::endl; % project[:arguments].each do |argument| std::cout << "<%= argument[:char] %>" << " : " << "<%= argument[:text] %>" << std::endl; % end return 0; }
Exemple 2:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 #include <cstdlib> #include <iostream> int main (int argc, char const* argv[]) { std::cout << "This is " << "Ruby pre-processor example" << " version " << "1.2" << " build date: " << "Thu May 08 20:17:38 +0200 2008" << std::endl << std::endl; std::cout << "Visit us at " << "http://www.speednova.cc" << std::endl << std::endl; std::cout << "Available arguments are listed below" << std::endl; std::cout << "h" << " : " << "Display RCPP's help." << std::endl; std::cout << "v" << " : " << "Display RCPP's version number." << std::endl; std::cout << "d" << " : " << "Deprecated argument." << std::endl; return 0; }
Imaginez que vous vouliez créer un tableau contenant toutes les lettres de l'alphabet. C'est assez simple dynamiquement, mais statiquement c'est un cauchemard avec le préprocesseur C++. Voici ce que celà donne avec RPP :
On profite des facilités des "range" de Ruby pour générer le tableau.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 // The old static way char alpha1[] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}; // The RPP inline way char alpha2[] = {'<% $src << ('a'..'z').to_a.join("','") %>'}; // The RPP way using an helper function (see rpphelpers.rb) char alpha4[] = <%= c_array ('a'..'z') %>; // The dynamic way for (int i=0; i<26; i++) std::cout << "letter " << lcase1[i] << std::endl; // The RPP way % ('a'..'z').each do |c| std::cout << "letter " << '<%= c %>' << std::endl; % end
Vous obtenez exemple2.cpp :
Exemple 3:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 // The old static way char alpha1[] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}; // The RPP inline way char alpha2[] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}; // The RPP way using an helper function (see rpphelpers.rb) char alpha4[] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}; // The dynamic way for (int i=0; i<26; i++) std::cout << "letter " << lcase1[i] << std::endl; // The RPP way std::cout << "letter " << 'a' << std::endl; std::cout << "letter " << 'b' << std::endl; std::cout << "letter " << 'c' << std::endl; std::cout << "letter " << 'd' << std::endl; std::cout << "letter " << 'e' << std::endl; std::cout << "letter " << 'f' << std::endl; std::cout << "letter " << 'g' << std::endl; std::cout << "letter " << 'h' << std::endl; std::cout << "letter " << 'i' << std::endl; std::cout << "letter " << 'j' << std::endl; std::cout << "letter " << 'k' << std::endl; std::cout << "letter " << 'l' << std::endl; std::cout << "letter " << 'm' << std::endl; std::cout << "letter " << 'n' << std::endl; std::cout << "letter " << 'o' << std::endl; std::cout << "letter " << 'p' << std::endl; std::cout << "letter " << 'q' << std::endl; std::cout << "letter " << 'r' << std::endl; std::cout << "letter " << 's' << std::endl; std::cout << "letter " << 't' << std::endl; std::cout << "letter " << 'u' << std::endl; std::cout << "letter " << 'v' << std::endl; std::cout << "letter " << 'w' << std::endl; std::cout << "letter " << 'x' << std::endl; std::cout << "letter " << 'y' << std::endl; std::cout << "letter " << 'z' << std::endl;
Il arrive souvent qu'ait des classes à créer et qu'on se retrouve à retaper le même code pour les accesseurs, les classes à hériter, etc... Voici un exemple de ce que celà donne avec RPP :
Ici on a créé (en quelques secondes) des helpers pour clarifier notre code.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 class Displayable { std::string toString() const = 0; }; class Starship { private: % declare_fields :serialNumber => :int, :name => :string, :leftEngine => :Engine, :rightEngine => :Engine public: % encapsulate :serialNumber => :int, :name => :string % getters :leftEngine => :Engine, :rightEngine => :Engine }; % classdef(:Warship, % :implements => [ :Starship, :Displayable ], % :fields => { :ammo => :int, :turret => :Weapon, :target => :Position }) { public: string toString() const { std::cout << "ammo =" << get_ammo() << std::endl; std::cout << "turret=" << get_turret() << std::endl; std::cout << "target=" << get_target() << std::endl; } % }
"declare_fields" prend en paramètre une suite de noms d'attributs, suivis de leur type, et génère leur déclaration.
"getters" génère des fonctions de la forme get_<nom attribut> et
"setters" génère des fonctions de la forme set_<nom_attribut>.
"encapsulate" génère les getters et les setters en une seule fois.
On a déjà économisé par mal de recopiage!
Pour encore plus de rapidité, le dernier exemple est radical, le helper "classdef" prend en paramètre le nom de la classe, les noms des classes à implémenter, et les attributs avec leurs types, et génère directement la classe avec les getters et setters appropriés. Comme on le voit dans l'exemple, vous pouvez tout de même rajouter ce que vous voulez dans le corps de la déclaration de la classe.
Voici ce que RPP a généré dans exemple3.cpp :
On pourrait aussi imaginer générer une batterie de constructeurs avec classdef, ce serait très rapide à faire.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 class Displayable { std::string toString() const = 0; }; class Starship { private: Engine rightEngine; string name; int serialNumber; Engine leftEngine; public: void set_name( const string& value ) { name = value; } void set_serialNumber( const int& value ) { serialNumber = value; } const string& get_name() const { return name; } const int& get_serialNumber() const { return serialNumber; } const Engine& get_rightEngine() const { return rightEngine; } const Engine& get_leftEngine() const { return leftEngine; } }; class Warship : public Starship, public Displayable { public: void set_turret( const Weapon& value ) { turret = value; } void set_target( const Position& value ) { target = value; } void set_ammo( const int& value ) { ammo = value; } const Weapon& get_turret() const { return turret; } const Position& get_target() const { return target; } const int& get_ammo() const { return ammo; } private: Weapon turret; Position target; int ammo; public: string toString() const { std::cout << "ammo =" << get_ammo() << std::endl; std::cout << "turret=" << get_turret() << std::endl; std::cout << "target=" << get_target() << std::endl; } };
Voilà pour la petite présentation, si vous voulez tester, voici un package comprenant RPP (Ruby PreProcessor) + les codes des exemples + les helpers des exemples + le fichier YAML des exemples.
J'espère que certains trouveront ca utileDans tous les cas n'hésitez pas à poster vos avis, demandes, ...
Partager