Coder une bibliothèque Scol en C - Niveau 2 » History » Version 1
iri, 01/29/2013 09:36 PM
1 | 1 | iri | h1. Coder une bibliothèque Scol en C - Niveau 2 |
---|---|---|---|
2 | |||
3 | Objectif : ajout d'éléments d'API, types Scol complexes |
||
4 | |||
5 | Moyens : |
||
6 | |||
7 | * manipuler des chaînes de caractères dans la pile; |
||
8 | * gérer des tuples; |
||
9 | * gérer des listes; |
||
10 | * gérer les listes de tuples; |
||
11 | * commenter avec doxygen; |
||
12 | * code source de la bibliothèque; |
||
13 | * exemple d'utilisation dans une application Scol. |
||
14 | |||
15 | STATUT : COMPLET |
||
16 | Version : 1.0, 1.0.1 |
||
17 | Date : novembre 2010, décembre 2010 (initialement diffusé sur http://www.irizone.net) |
||
18 | Auteur : iri |
||
19 | Licence du tutoriel : GNU FDL v1.3 |
||
20 | Licence du code source : GNU/GPL v3 |
||
21 | Téléchargement : http://www.irizone.net/dl/tutos/vmscol/vm_example_2.tar.gz |
||
22 | Langage : *C* |
||
23 | Ok pour toute version de Scol Windows et GNU/Linux (>= 4.0). |
||
24 | |||
25 | Pré-requis : [[Coder une bibliothèque Scol en C - Niveau 1]] |
||
26 | |||
27 | |||
28 | h2. Les chaînes de caractères |
||
29 | |||
30 | Les chaînes requierent un traitement un peu particulier. Rien de bien sorcier, |
||
31 | rassurez-vous ! Différentes fonctions et macros sont là pour vous aider :) |
||
32 | |||
33 | Première chose, récupérer le pointeur dans la pile. Ici, aucune différence, |
||
34 | ce sera soit <em>MMpull</em> soit <em>MMget</em> selon ce qu'on veut faire. |
||
35 | On utilisera la macro <em>MTOP</em> pour convertir le pointeur de la machine Scol |
||
36 | en un pointeur système plus facilement manipulable. On aura ainsi : |
||
37 | |||
38 | <pre>int p_chaine = MTOP (MMpull (m)); |
||
39 | int p_chaine = MTOP (MMget (m, X));</pre> |
||
40 | |||
41 | Seconde étape, récupérer la chaîne elle-même grâce à _MMstartstr_ qui |
||
42 | retourne le curseur à la position 0 de la chaîne : |
||
43 | |||
44 | <pre>char * chaine = MMstartstr (m, p_chaine);</pre> |
||
45 | |||
46 | On peut faire subir à _chaine_ tous les traitments C qu'on souhaite. |
||
47 | |||
48 | Cependant, gardez en mémoire que si vous devez allouer de la mémoire pour un |
||
49 | traitement particulier (typiquement un _malloc_ mais ce peut être aussi une allocation |
||
50 | "cachée" telle que _strdup_) vous devez libérer ce que vous avez alloué, Scol ne |
||
51 | le fera pas à votre place. |
||
52 | En revanche, vous devez libérer une fois le contenu passé à la pile Scol : à ce |
||
53 | moment là,la donnée sera allouée dans la bande Scol, indépendamment de votre propre |
||
54 | allocation. |
||
55 | |||
56 | Au final, pour retourner une chaine de caractères de type _char *_, (vous |
||
57 | pouvez bien sur caster si cela a un sens), la fonction _Mpushstrbloc_ doit |
||
58 | être utilisée : |
||
59 | |||
60 | <pre>Mpushstrbloc (m, chaine_finale);</pre> |
||
61 | |||
62 | |||
63 | h3. Exemple |
||
64 | |||
65 | Une fonction stupide pour souhaiter la bienvenue (un remake du fameux "Hello |
||
66 | Word" !) : la fonction Scol attend un nom et retourne la chaine "Hello _le_nom_entré_". |
||
67 | Rien de bien transcendant donc ! |
||
68 | |||
69 | Décortiquons en pseduo-code : |
||
70 | |||
71 | |||
72 | # DEPILE l'argument chaine |
||
73 | # PLACE lecurseur en début de chaine |
||
74 | # traitements |
||
75 | # EMPILE le résultat |
||
76 | |||
77 | |||
78 | Concrètement, cela va donner : |
||
79 | |||
80 | <pre>menter = MTOP (MMpull (m)); /* 1 */ |
||
81 | enter = MMstartstr (m, menter); /* 2 */ |
||
82 | sprintf (result, "%s %s", "Hello", enter); /* 3 */ |
||
83 | Mpushstrbloc (m, result); /* 4 */</pre> |
||
84 | |||
85 | Le code complet de la fonction est alors : |
||
86 | |||
87 | <pre>int sc_hello (mmachine m) |
||
88 | { |
||
89 | int menter; |
||
90 | char * enter = NULL; |
||
91 | char * result = NULL; |
||
92 | |||
93 | MMechostr (MSKDEBUG, "sc_hello : enteringn"); |
||
94 | |||
95 | menter = MTOP (MMpull (m)); |
||
96 | enter = MMstartstr (m, menter); |
||
97 | |||
98 | result = (char *) malloc (sizeof (char) * (strlen (enter) +1)); |
||
99 | sprintf (result, "%s %s", "Hello", enter); |
||
100 | |||
101 | Mpushstrbloc (m, result); |
||
102 | free (result); |
||
103 | return 0; |
||
104 | }</pre> |
||
105 | |||
106 | Bien entendu, on alloue dynamiquement la mémoire nécessaire à la chaine selon |
||
107 | la longueur de la chaine d'entrée et on libère ensuite. |
||
108 | |||
109 | Le type de la fonction Scol est : <code>fun [S] S</code> |
||
110 | |||
111 | |||
112 | h2. Les tuples |
||
113 | |||
114 | Les tuples sont des sortes de tableaux : un seul élément dans la pile contient |
||
115 | plusieurs variables distinctes. |
||
116 | |||
117 | h3. Méthode |
||
118 | |||
119 | Pour récupérer les variables d'un tuple passé en argument d'une fonction Scol, |
||
120 | on commence logiquement par dépiler ledit tuple via les fonctions habituelles. Puis, |
||
121 | le nombre de variables contenues étant connu (c'est un tuple, pas une liste !), |
||
122 | on les extraie une à une (ou via |
||
123 | une boucle dans un tableau si le tuple est large, par exemple) grâce à la fonction |
||
124 | _MMfetch_. Celle-ci est, dans le concept, similaire à _MMget_ que |
||
125 | vous connaissez : |
||
126 | |||
127 | <pre>MMetch machine_scol tuple index_dans_le_tuple</pre> |
||
128 | |||
129 | sachant que l'index démarre à zéro pour le premier élément. |
||
130 | |||
131 | En pseudo-code, pour un tuple de 3 éléments, on a : |
||
132 | |||
133 | <pre>tuple = MMpull (m) |
||
134 | v1 = MMfetch (m, tuple, 0); |
||
135 | v2 = MMfetch (m, tuple, 1); |
||
136 | v3 = MMfetch (m, tuple, 2);</pre> |
||
137 | |||
138 | Pour retourner un tuple, c'est différent mais on garde une logique semblable. |
||
139 | On commence donc par empiler chaque élément du tuple dans l'ordre du type de la |
||
140 | fonction Scol. Puis on empile le nombre d'éléments du typle et on demande enfin |
||
141 | à Scol de créer le tuple. Au final, pour retourner un tuple de trois valeurs, le |
||
142 | pseudo-code sera : |
||
143 | |||
144 | <pre>MMpush (m, PTOM (v1)) |
||
145 | Mpushstrbloc (m, PTOM (v2)) |
||
146 | MMpush (m, PTOM (v3)) |
||
147 | MMpush (m, ITOM (3)) |
||
148 | MBdeftab (m)</pre> |
||
149 | |||
150 | |||
151 | h3. Exemple |
||
152 | |||
153 | Notre fonction Scol combinera des tuples en entrée et en sortie. Elle se propose |
||
154 | depuis deux entiers de retourner deux autres entiers qui seront respectivement leur |
||
155 | quotient et leur reste. Soit le prototypage Scol de cette fonction : |
||
156 | |||
157 | <code>fun [[I I]] [I I]</code> |
||
158 | |||
159 | L'extraction des deux entiers d'entrée sera : |
||
160 | |||
161 | <pre>mtuple = MTOI (MMpull (m)); |
||
162 | mdividende = MTOI (MMfetch (m, mtuple, 0)); |
||
163 | mdiviseur = MTOI (MMfetch (m, mtuple, 1));</pre> |
||
164 | |||
165 | Le tuple retournée est codée ainsi : |
||
166 | |||
167 | <pre>MMpush (m, ITOM (quotient)); |
||
168 | MMpush (m, ITOM (reste)); |
||
169 | MMpush (m, ITOM (2)); |
||
170 | return MBdeftab (m);</pre> |
||
171 | |||
172 | D'où le code complet de la fonction : |
||
173 | |||
174 | <pre>int sc_division (mmachine m) |
||
175 | { |
||
176 | int mtuple, mdividende, mdiviseur; |
||
177 | int quotient, reste; |
||
178 | |||
179 | MMechostr (MSKDEBUG, "sc_division : enteringn"); |
||
180 | |||
181 | mtuple = MTOI (MMpull (m)); |
||
182 | mdividende = MTOI (MMfetch (m, mtuple, 0)); |
||
183 | mdiviseur = MTOI (MMfetch (m, mtuple, 1)); |
||
184 | |||
185 | if (mdiviseur == 0) /* on teste le cas de la division par zéro */ |
||
186 | return MMpush (m, NIL); |
||
187 | |||
188 | quotient = mdividende / mdiviseur; |
||
189 | reste = mdividende % mdiviseur; |
||
190 | |||
191 | MMpush (m, ITOM (quotient)); |
||
192 | MMpush (m, ITOM (reste)); |
||
193 | MMpush (m, ITOM (2)); |
||
194 | return MBdeftab (m); |
||
195 | }</pre> |
||
196 | |||
197 | |||
198 | h2. Les listes simples et les listes de tuples |
||
199 | |||
200 | Petit rappel sur les listes en Scol : une liste possède une structure similaire |
||
201 | aux listes chainées dans leur principe. Une liste est un tuple de deux éléments : |
||
202 | le premier élément de la liste et la liste de la suite des autres éléments. Soit : |
||
203 | <code>liste = [element suite]</code> |
||
204 | |||
205 | Nous retrouverons donc cette structure dans la façon de coder la récupération |
||
206 | d'une liste ou le retour d'une liste. |
||
207 | |||
208 | En Scol, on code souvent les listes de cette manière : |
||
209 | <code>liste = 1 :: 2 :: 3 :: 4 :: 5 :: nil;</code> |
||
210 | |||
211 | Cette manière est considérée à raison comme plus lisible mais elle cache la structure |
||
212 | sous-jacente. Pour mieux se la représenter au niveau du code source, écrivons cette |
||
213 | liste avec l'écriture standard : |
||
214 | <code>liste = [1 [2 [3 [4 [5 nil]]]]];</code> |
||
215 | on voit bien l'imbrication de tuples... |
||
216 | |||
217 | Conséquence : pour lire ou écrire des listes, faudra boucler ! |
||
218 | En lecture, le principe est le suivant : |
||
219 | |||
220 | <pre>WHILE (liste non vide) |
||
221 | e = premier élément de liste |
||
222 | SI e non vide |
||
223 | instructions |
||
224 | liste = suite de la liste</pre> |
||
225 | |||
226 | on pourra avoir des imbrications : liste dans une liste ou, moins rarement, une liste |
||
227 | de tuples auquel il faudra réaliser une boucle secondaire dans <code>instructions</code> |
||
228 | (cf code source dans l'archive téléchargeable). |
||
229 | |||
230 | En écriture, c'est un peu différent : |
||
231 | <pre>WHILE (liste_traitée) |
||
232 | { |
||
233 | values = liste_traitée->data |
||
234 | x = values->x |
||
235 | y = values->y |
||
236 | z = values->z |
||
237 | MMpush (m, PTOM (x)) |
||
238 | MMpush (m, PTOM (y)) |
||
239 | MMpush (m, PTOM (z)) |
||
240 | MMpush (m, ITOM (3)) |
||
241 | MBdeftab (m) |
||
242 | compteur++ |
||
243 | liste_traitée = liste_traitée->next |
||
244 | } |
||
245 | MMpush (m, NIL) |
||
246 | FOR (i=0; i < compteur; i++) |
||
247 | { |
||
248 | MMpush (m, ITOM (2)) |
||
249 | MBdeftab (m) |
||
250 | }</pre> |
||
251 | |||
252 | On obtient alors un type Scol <code>[[u0 u1 u2] r1]</code> : une liste de tuples |
||
253 | de trois éléments (cf même code source que précédemment). |
||
254 | Le principe est malgré tout simple : on empile d'abord chaque élément de la liste, |
||
255 | que ce soit un simple entier ou un tuple complexe. Puis on ajoute un NIL final |
||
256 | (rappellez-vous qu'une liste se termine toujours par un 'nil'). Enfin, on crée nos |
||
257 | tuples à 2 éléments (rappelez-vous, une liste c'est <code>[a [b [c [d [e nil]]]]]</code>, soit <code>[a next]</code>). |
||
258 | |||
259 | |||
260 | h3. Exemple |
||
261 | |||
262 | Imaginons une fonction qui accepte une liste d'entiers en entrée et qui retourne |
||
263 | une même liste avec les entiers incrémentés ou décrémentés selon un flag d'une certaine |
||
264 | valeur passée elle-aussi en paramètre : |
||
265 | <code>fun [[I r1] I I] [I r1]</code> |
||
266 | |||
267 | Pour réaliser cela, nous devons dépiler les trois arguments, le dernier argument |
||
268 | étant le dernier sur la pile : |
||
269 | <pre>mflag = MTOI (MMpull (m)); |
||
270 | mnumber = MTOI (MMpull (m)); |
||
271 | mlist_enter = MMpull (m)>>1;</pre> |
||
272 | |||
273 | mlist_enter doit désormais être lue (elle contient la liste d'entiers) : |
||
274 | <pre> while (mlist_enter != NIL) |
||
275 | { |
||
276 | element_list = MTOI (MMfetch (m, mlist_enter, 0)); |
||
277 | if (element_list != NIL) |
||
278 | { |
||
279 | // traitements sur element_list |
||
280 | } |
||
281 | mlist_enter = MTOI (MMfetch(m, mlist_enter, 1)); |
||
282 | }</pre> |
||
283 | |||
284 | nous bouclons tant que la liste ne vaut pas nil. Chaque élément est lu grâce à la |
||
285 | fonction <em>MMfetch</em> que nous avons déjà vu. Cet élément est toujours à la position |
||
286 | 0 du tuple, la suite de la liste étant à la position 1 (relisez le paragraphe plus |
||
287 | haut sinon). |
||
288 | |||
289 | Notez que nous ne pouvons pas lire les valeurs de manière directe avec <em>MMfetch</em> |
||
290 | comme nous l'avons effectué avec les tuples car cette fois nous ne connaissons pas le nombre |
||
291 | d'éléments a priori contenus dans la liste. Nous devons donc passer par une boucle WHILE. |
||
292 | |||
293 | Pour la sortie, nous devons également boucler : |
||
294 | <pre>while (mlist_out != NIL) |
||
295 | { |
||
296 | element_list = MTOI (MMfetch (m, mlist_out, 0)); |
||
297 | if (element_list != NIL) |
||
298 | { |
||
299 | MMpush (m, ITOM (element_list)); |
||
300 | compteur++; |
||
301 | } |
||
302 | mlist_out = MTOI (MMfetch(m, mlist_out, 1)); |
||
303 | } |
||
304 | MMpush (m, NIL); |
||
305 | for (i = 0; i < compteur; i++) |
||
306 | { |
||
307 | MMpush (m, ITOM (2)); |
||
308 | MBdeftab (m); |
||
309 | } |
||
310 | return 0;</pre> |
||
311 | |||
312 | Chaque élément est d'abord empilés puis la liste est construite, comme décrit plus haut. |
||
313 | |||
314 | Au final, la fonction est : |
||
315 | <pre>int sc_calcul_list (mmachine m) |
||
316 | { |
||
317 | int mlist_enter, mnumber, mflag; |
||
318 | int element_list, i, compteur = 0; |
||
319 | |||
320 | MMechostr (MSKDEBUG, "sc_calcul_list : enteringn"); |
||
321 | |||
322 | mflag = MTOI (MMpull (m)); |
||
323 | mnumber = MTOI (MMpull (m)); |
||
324 | mlist_enter = MMpull (m)>>1; |
||
325 | |||
326 | while (mlist_enter != NIL) |
||
327 | { |
||
328 | element_list = MTOI (MMfetch (m, mlist_enter, 0)); |
||
329 | if (element_list != NIL) |
||
330 | { |
||
331 | MMpush (m, ITOM (element_list + (mnumber * mflag))); |
||
332 | compteur++; |
||
333 | } |
||
334 | mlist_enter = MTOI (MMfetch(m, mlist_enter, 1)); |
||
335 | } |
||
336 | |||
337 | MMpush (m, NIL); |
||
338 | |||
339 | for (i = 0; i < compteur; i++) |
||
340 | { |
||
341 | MMpush (m, ITOM (2)); |
||
342 | MBdeftab (m); |
||
343 | } |
||
344 | return 0; |
||
345 | }</pre> |
||
346 | |||
347 | Notez que pour alléger le code C, la boucle WHILE traite à la fois la lecture d'entrée |
||
348 | et l'écriture de sortie. Nous verrons plus loin comment est géré le flag Scol. |
||
349 | |||
350 | h2. Code source de la bibliothèque |
||
351 | |||
352 | La dernière fonction comprend un "flag" Scol. il y a plusieurs méthodes en C |
||
353 | pour les gérer, voyez le code source téléchargeable. Le principe est le suivant : |
||
354 | |||
355 | # dans le tableau des nom Scol, on définit son nom (par exemple "EXEMPLE_PLUS"), |
||
356 | # dans le tableau 2, on définit sa valeur, |
||
357 | # dans le tableau 3, on définit son type, ici _TYPVAR_ car c'est une valeur |
||
358 | numérique, |
||
359 | # dans le dernier tableau, on donne son type Scol, ici un entier donc "I". |
||
360 | |||
361 | Si on compte également utiliser le flag dans le code source (à des fins de comparaison |
||
362 | par exemple), la méthode la plus simple est alors d'ajouter une définition dans le header : |
||
363 | |||
364 | @#define EXEMPLE_PLUS 1@ |
||
365 | |||
366 | Ci-dessous est simplement recopiée la nouvelle définition de l'API de ce niveau 2 :</p> |
||
367 | <div class="code2"><pre>char* example_name[EXAMPLE_PKG_NB] = |
||
368 | { |
||
369 | /* nouvelles fonctions Scol */ |
||
370 | "example_increment", /* fonction de l'API appelable depuis l'extérieur */ |
||
371 | "example_hello", |
||
372 | "example_calcul", |
||
373 | "example_list", |
||
374 | |||
375 | /* nouveaux flags Scol */ |
||
376 | "EXAMPLE_MOINS", |
||
377 | "EXAMPLE_PLUS" |
||
378 | }; |
||
379 | |||
380 | int (*example_fun[EXAMPLE_PKG_NB]) (mmachine m) = |
||
381 | { |
||
382 | sc_increment, /* nom interne où le code de cette fonction est développé */ |
||
383 | sc_hello, |
||
384 | sc_division, |
||
385 | sc_calcul_list, |
||
386 | |||
387 | (void*) ((-1)*2), /* vaut '-1', on décrémente */ |
||
388 | (void*) (1*2) /* vaut '+1', on incrémente */ |
||
389 | }; |
||
390 | |||
391 | int example_narg[EXAMPLE_PKG_NB] = |
||
392 | { |
||
393 | 1, /* nombre d'argument attendu par cette fonction */ |
||
394 | 1, |
||
395 | 1, |
||
396 | 3, |
||
397 | |||
398 | TYPVAR, |
||
399 | TYPVAR |
||
400 | }; |
||
401 | |||
402 | char* example_type[EXAMPLE_PKG_NB] = |
||
403 | { |
||
404 | "fun [I] I", /* prototype Scol de cette fonction : on notera que le nombre d'argument est bien 1 */ |
||
405 | "fun [S] S", |
||
406 | "fun [[I I]] [I I]", |
||
407 | "fun [[I r1] I I] [I r1]", |
||
408 | |||
409 | "I", |
||
410 | "I" |
||
411 | };</pre> |
||
412 | |||
413 | Le code entier est disponible, comme toujours, dans l'archive téléchargeable ! |
||
414 | |||
415 | h2. Exemples Scol |
||
416 | |||
417 | h3. Fonction "example_hello" : |
||
418 | |||
419 | <pre>fun main ()= |
||
420 | _showconsole; |
||
421 | _fooS example_hello "iri"; |
||
422 | 0;; |
||
423 | </pre> |
||
424 | |||
425 | h3. Fonction "example_calcul" : |
||
426 | |||
427 | <pre>fun main ()= |
||
428 | _showconsole; |
||
429 | let example_calcul [17 3] -> [quotient reste] in |
||
430 | _fooS strcatn "La division de 17 par 3 donne un quotient de " :: |
||
431 | (itoa quotient) :: |
||
432 | " et un reste de " :: |
||
433 | (itoa reste) :: nil; |
||
434 | 0;; |
||
435 | </pre> |
||
436 | |||
437 | La console devrait afficher : <code>La division de 17 par 3 donne un quotient de 5 et un reste de 3</code> |
||
438 | |||
439 | h3. Fonction "example_list" : |
||
440 | |||
441 | <pre>fun main ()= |
||
442 | _showconsole; |
||
443 | _fooIList example_list 7 :: 5 :: 2 :: nil 10 EXAMPLE_PLUS; |
||
444 | 0;; |
||
445 | </pre> |
||
446 | |||
447 | La console devrait afficher en retour : <code>17 : 15 : 12</code> |
||
448 | |||
449 | Le même exemple mais en changeant de flag : |
||
450 | <pre>fun main ()= |
||
451 | _showconsole; |
||
452 | _fooIList example_list 7 :: 5 :: 2 :: nil 10 EXAMPLE_MOINS; |
||
453 | 0;; |
||
454 | </pre> |
||
455 | |||
456 | La console devrait afficher en retour : <code>-3: -5 : -8</code></p> |
||
457 | |||
458 | h2. Documenter avec Doxygen |
||
459 | |||
460 | Commenter son code source, c'est bien, le documenter c'est encore mieux ! |
||
461 | |||
462 | Il existe plusieurs outils. J'utilise (et les autres développeurs du langage |
||
463 | aussi) "Doxygen":http://www.doxygen.org/, un outil complet et pratique. |
||
464 | Il permet d'exporter une documentation en plusieurs formats : du html au pdf en |
||
465 | passant par Latex et plusieurs autres. Si vous utilisez Code::Blocks, le plugin |
||
466 | est désormais intégré dans la barre de menu. Autrement, vous trouverez de l'information |
||
467 | à cette adresse : "Wiki C::B":http://wiki.codeblocks.org/index.php?title=DoxyBlocks. |
||
468 | Sous le nom de menu _DoxyBlocks_, vous configurez la sortie selon vos souhaits |
||
469 | et lancez la génération des documents via l'item "Extract documentation" (ou en |
||
470 | lançant "DoxyWizard" si vous désirez plus d'options avec l'assistant graphique). C'est |
||
471 | simple, rapide et efficace. |
||
472 | La documentation générée par cet outil est également incluse dans l'archive. |
||
473 | |||
474 | En amont, il est nécessaire de suivre une certaine syntaxe dans les commentaires. |
||
475 | Vous la trouverez dans le code source de l'archive téléchargeable, pour la plus |
||
476 | courante.Pour aller plus en profondeur, consultez le site de "Doxygen":http://www.doxygen.org/. |
||
477 | |||
478 | Enfin, même si vous n'utilisez pas C::B, vous pouvez utiliser Doxygen pour générer |
||
479 | vos documentations ! |
||
480 | |||
481 | Remarque : C::B intègre aussi un plugin pour Valgrind permettant de tester les |
||
482 | utilisations mémoire de votre application. |