Project

General

Profile

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.