Project

General

Profile

Actions

Coder une bibliothèque Scol en C - Niveau 2

Objectif : ajout d'éléments d'API, types Scol complexes

Moyens :

  • manipuler des chaînes de caractères dans la pile;
  • gérer des tuples;
  • gérer des listes;
  • gérer les listes de tuples;
  • commenter avec doxygen;
  • code source de la bibliothèque;
  • exemple d'utilisation dans une application Scol.

STATUT : COMPLET
Version : 1.0, 1.0.1
Date : novembre 2010, décembre 2010 (initialement diffusé sur http://www.irizone.net)
Auteur : iri
Licence du tutoriel : GNU FDL v1.3
Licence du code source : GNU/GPL v3
Téléchargement : http://www.irizone.net/dl/tutos/vmscol/vm_example_2.tar.gz
Langage : C
Ok pour toute version de Scol Windows et GNU/Linux (>= 4.0).

Pré-requis : Coder une bibliothèque Scol en C - Niveau 1

Les chaînes de caractères

Les chaînes requierent un traitement un peu particulier. Rien de bien sorcier,
rassurez-vous ! Différentes fonctions et macros sont là pour vous aider :)

Première chose, récupérer le pointeur dans la pile. Ici, aucune différence,
ce sera soit <em>MMpull</em> soit <em>MMget</em> selon ce qu'on veut faire.
On utilisera la macro <em>MTOP</em> pour convertir le pointeur de la machine Scol
en un pointeur système plus facilement manipulable. On aura ainsi :

int p_chaine = MTOP (MMpull (m));
int p_chaine = MTOP (MMget (m, X));

Seconde étape, récupérer la chaîne elle-même grâce à MMstartstr qui
retourne le curseur à la position 0 de la chaîne :

char * chaine = MMstartstr (m, p_chaine);

On peut faire subir à chaine tous les traitments C qu'on souhaite.

Cependant, gardez en mémoire que si vous devez allouer de la mémoire pour un
traitement particulier (typiquement un malloc mais ce peut être aussi une allocation
"cachée" telle que strdup) vous devez libérer ce que vous avez alloué, Scol ne
le fera pas à votre place.
En revanche, vous devez libérer une fois le contenu passé à la pile Scol : à ce
moment là,la donnée sera allouée dans la bande Scol, indépendamment de votre propre
allocation.

Au final, pour retourner une chaine de caractères de type char *, (vous
pouvez bien sur caster si cela a un sens), la fonction Mpushstrbloc doit
être utilisée :

Mpushstrbloc (m, chaine_finale);

Exemple

Une fonction stupide pour souhaiter la bienvenue (un remake du fameux "Hello
Word" !) : la fonction Scol attend un nom et retourne la chaine "Hello le_nom_entré".
Rien de bien transcendant donc !

Décortiquons en pseduo-code :

  1. DEPILE l'argument chaine
  2. PLACE lecurseur en début de chaine
  3. traitements
  4. EMPILE le résultat

Concrètement, cela va donner :

menter = MTOP (MMpull (m));        /* 1 */
enter = MMstartstr (m, menter);        /* 2 */
sprintf (result, "%s %s", "Hello", enter);    /* 3 */
Mpushstrbloc (m, result);    /* 4 */

Le code complet de la fonction est alors :

int sc_hello (mmachine m)
{
    int menter;
    char * enter = NULL;
    char * result = NULL;

    MMechostr (MSKDEBUG, "sc_hello : enteringn");

    menter = MTOP (MMpull (m));
    enter = MMstartstr (m, menter);

    result = (char *) malloc (sizeof (char) * (strlen (enter) +1));
    sprintf (result, "%s %s", "Hello", enter);

    Mpushstrbloc (m, result);
    free (result);
    return 0;
}

Bien entendu, on alloue dynamiquement la mémoire nécessaire à la chaine selon
la longueur de la chaine d'entrée et on libère ensuite.

Le type de la fonction Scol est : fun [S] S

Les tuples

Les tuples sont des sortes de tableaux : un seul élément dans la pile contient
plusieurs variables distinctes.

Méthode

Pour récupérer les variables d'un tuple passé en argument d'une fonction Scol,
on commence logiquement par dépiler ledit tuple via les fonctions habituelles. Puis,
le nombre de variables contenues étant connu (c'est un tuple, pas une liste !),
on les extraie une à une (ou via
une boucle dans un tableau si le tuple est large, par exemple) grâce à la fonction
MMfetch. Celle-ci est, dans le concept, similaire à MMget que
vous connaissez :

MMetch machine_scol tuple index_dans_le_tuple

sachant que l'index démarre à zéro pour le premier élément.

En pseudo-code, pour un tuple de 3 éléments, on a :

tuple = MMpull (m)
v1 = MMfetch (m, tuple, 0);
v2 = MMfetch (m, tuple, 1);
v3 = MMfetch (m, tuple, 2);

Pour retourner un tuple, c'est différent mais on garde une logique semblable.
On commence donc par empiler chaque élément du tuple dans l'ordre du type de la
fonction Scol. Puis on empile le nombre d'éléments du typle et on demande enfin
à Scol de créer le tuple. Au final, pour retourner un tuple de trois valeurs, le
pseudo-code sera :

MMpush (m, PTOM (v1))
Mpushstrbloc (m, PTOM (v2))
MMpush (m, PTOM (v3))
MMpush (m, ITOM (3))
MBdeftab (m)

Exemple

Notre fonction Scol combinera des tuples en entrée et en sortie. Elle se propose
depuis deux entiers de retourner deux autres entiers qui seront respectivement leur
quotient et leur reste. Soit le prototypage Scol de cette fonction :

fun [[I I]] [I I]

L'extraction des deux entiers d'entrée sera :

mtuple = MTOI (MMpull (m));
mdividende = MTOI (MMfetch (m, mtuple, 0));
mdiviseur = MTOI (MMfetch (m, mtuple, 1));

Le tuple retournée est codée ainsi :

MMpush (m, ITOM (quotient));
MMpush (m, ITOM (reste));
MMpush (m, ITOM (2));
return MBdeftab (m);

D'où le code complet de la fonction :

int sc_division (mmachine m)
{
    int mtuple, mdividende, mdiviseur;
    int quotient, reste;

    MMechostr (MSKDEBUG, "sc_division : enteringn");

    mtuple = MTOI (MMpull (m));
    mdividende = MTOI (MMfetch (m, mtuple, 0));
    mdiviseur = MTOI (MMfetch (m, mtuple, 1));

    if (mdiviseur == 0)     /* on teste le cas de la division par zéro */
        return MMpush (m, NIL);

    quotient = mdividende / mdiviseur;
    reste = mdividende % mdiviseur;

    MMpush (m, ITOM (quotient));
    MMpush (m, ITOM (reste));
    MMpush (m, ITOM (2));
    return MBdeftab (m);
}

Les listes simples et les listes de tuples

Petit rappel sur les listes en Scol : une liste possède une structure similaire
aux listes chainées dans leur principe. Une liste est un tuple de deux éléments :
le premier élément de la liste et la liste de la suite des autres éléments. Soit :
liste = [element suite]

Nous retrouverons donc cette structure dans la façon de coder la récupération
d'une liste ou le retour d'une liste.

En Scol, on code souvent les listes de cette manière :
liste = 1 :: 2 :: 3 :: 4 :: 5 :: nil;

Cette manière est considérée à raison comme plus lisible mais elle cache la structure
sous-jacente. Pour mieux se la représenter au niveau du code source, écrivons cette
liste avec l'écriture standard :
liste = [1 [2 [3 [4 [5 nil]]]]];
on voit bien l'imbrication de tuples...

Conséquence : pour lire ou écrire des listes, faudra boucler !
En lecture, le principe est le suivant :

WHILE (liste non vide)
        e = premier élément de liste
        SI e non vide
            instructions
        liste = suite de la liste

on pourra avoir des imbrications : liste dans une liste ou, moins rarement, une liste
de tuples auquel il faudra réaliser une boucle secondaire dans instructions
(cf code source dans l'archive téléchargeable).

En écriture, c'est un peu différent :

WHILE (liste_traitée)
       {
          values = liste_traitée->data
          x = values->x
          y = values->y
          z = values->z
          MMpush (m, PTOM (x))
          MMpush (m, PTOM (y))
          MMpush (m, PTOM (z))
          MMpush (m, ITOM (3))
          MBdeftab (m)
          compteur++
          liste_traitée = liste_traitée->next
      }
  MMpush (m, NIL)
  FOR (i=0; i < compteur; i++)
      {
          MMpush (m, ITOM (2))
          MBdeftab (m)
      }

On obtient alors un type Scol [[u0 u1 u2] r1] : une liste de tuples
de trois éléments (cf même code source que précédemment).
Le principe est malgré tout simple : on empile d'abord chaque élément de la liste,
que ce soit un simple entier ou un tuple complexe. Puis on ajoute un NIL final
(rappellez-vous qu'une liste se termine toujours par un 'nil'). Enfin, on crée nos
tuples à 2 éléments (rappelez-vous, une liste c'est [a [b [c [d [e nil]]]]], soit [a next]).

Exemple

Imaginons une fonction qui accepte une liste d'entiers en entrée et qui retourne
une même liste avec les entiers incrémentés ou décrémentés selon un flag d'une certaine
valeur passée elle-aussi en paramètre :
fun [[I r1] I I] [I r1]

Pour réaliser cela, nous devons dépiler les trois arguments, le dernier argument
étant le dernier sur la pile :

mflag = MTOI (MMpull (m));
mnumber = MTOI (MMpull (m));
mlist_enter = MMpull (m)>>1;

mlist_enter doit désormais être lue (elle contient la liste d'entiers) :

 while (mlist_enter != NIL)
    {
         element_list = MTOI (MMfetch (m, mlist_enter, 0));
         if (element_list != NIL)
         {
             // traitements sur element_list
         }
         mlist_enter = MTOI (MMfetch(m, mlist_enter, 1));
    }

nous bouclons tant que la liste ne vaut pas nil. Chaque élément est lu grâce à la
fonction <em>MMfetch</em> que nous avons déjà vu. Cet élément est toujours à la position
0 du tuple, la suite de la liste étant à la position 1 (relisez le paragraphe plus
haut sinon).

Notez que nous ne pouvons pas lire les valeurs de manière directe avec <em>MMfetch</em>
comme nous l'avons effectué avec les tuples car cette fois nous ne connaissons pas le nombre
d'éléments a priori contenus dans la liste. Nous devons donc passer par une boucle WHILE.

Pour la sortie, nous devons également boucler :

while (mlist_out != NIL)
    {
         element_list = MTOI (MMfetch (m, mlist_out, 0));
         if (element_list != NIL)
         {
             MMpush (m, ITOM (element_list));
             compteur++;
         }
         mlist_out = MTOI (MMfetch(m, mlist_out, 1));
    }
    MMpush (m, NIL);
    for (i = 0; i  < compteur; i++)
    {
        MMpush (m, ITOM (2));
        MBdeftab (m);
    }
    return 0;

Chaque élément est d'abord empilés puis la liste est construite, comme décrit plus haut.

Au final, la fonction est :

int sc_calcul_list (mmachine m)
{
    int mlist_enter, mnumber, mflag;
    int element_list, i, compteur = 0;

    MMechostr (MSKDEBUG, "sc_calcul_list : enteringn");

    mflag = MTOI (MMpull (m));
    mnumber = MTOI (MMpull (m));
    mlist_enter = MMpull (m)>>1;

    while (mlist_enter != NIL)
    {
         element_list = MTOI (MMfetch (m, mlist_enter, 0));
         if (element_list != NIL)
         {
             MMpush (m, ITOM (element_list + (mnumber * mflag)));
             compteur++;
         }
         mlist_enter = MTOI (MMfetch(m, mlist_enter, 1));
    }

    MMpush (m, NIL);

    for (i = 0; i  < compteur; i++)
    {
        MMpush (m, ITOM (2));
        MBdeftab (m);
    }
    return 0;
}

Notez que pour alléger le code C, la boucle WHILE traite à la fois la lecture d'entrée
et l'écriture de sortie. Nous verrons plus loin comment est géré le flag Scol.

Code source de la bibliothèque

La dernière fonction comprend un "flag" Scol. il y a plusieurs méthodes en C
pour les gérer, voyez le code source téléchargeable. Le principe est le suivant :

  1. dans le tableau des nom Scol, on définit son nom (par exemple "EXEMPLE_PLUS"),
  2. dans le tableau 2, on définit sa valeur,
  3. dans le tableau 3, on définit son type, ici TYPVAR car c'est une valeur
    numérique,
  4. dans le dernier tableau, on donne son type Scol, ici un entier donc "I".

Si on compte également utiliser le flag dans le code source (à des fins de comparaison
par exemple), la méthode la plus simple est alors d'ajouter une définition dans le header :

#define EXEMPLE_PLUS 1

Ci-dessous est simplement recopiée la nouvelle définition de l'API de ce niveau 2 :</p>
<div class="code2">

char* example_name[EXAMPLE_PKG_NB] =
{
    /* nouvelles fonctions Scol */
    "example_increment",    /* fonction de l'API appelable depuis l'extérieur */
    "example_hello",
    "example_calcul",
    "example_list",

    /* nouveaux flags Scol */
    "EXAMPLE_MOINS",
    "EXAMPLE_PLUS" 
};

int (*example_fun[EXAMPLE_PKG_NB]) (mmachine m) =
{
    sc_increment,   /* nom interne où le code de cette fonction est développé */
    sc_hello,
    sc_division,
    sc_calcul_list,

    (void*) ((-1)*2),   /* vaut '-1', on décrémente */
    (void*) (1*2)    /* vaut '+1', on incrémente */
};

int example_narg[EXAMPLE_PKG_NB] =
{
    1,      /* nombre d'argument attendu par cette fonction */
    1,
    1,
    3,

    TYPVAR,
    TYPVAR
};

char* example_type[EXAMPLE_PKG_NB] =
{
    "fun [I] I",    /* prototype Scol de cette fonction : on notera que le nombre d'argument est bien 1 */
    "fun [S] S",
    "fun [[I I]] [I I]",
    "fun [[I r1] I I] [I r1]",

    "I",
    "I" 
};

Le code entier est disponible, comme toujours, dans l'archive téléchargeable !

Exemples Scol

Fonction "example_hello" :

fun main ()=
    _showconsole;
    _fooS example_hello "iri";
    0;;

Fonction "example_calcul" :

fun main ()=
    _showconsole;
    let example_calcul [17 3] -> [quotient reste] in
    _fooS strcatn "La division de 17 par 3 donne un quotient de " ::
            (itoa quotient) ::
            " et un reste de " ::
            (itoa reste) :: nil;
    0;;

La console devrait afficher : La division de 17 par 3 donne un quotient de 5 et un reste de 3

Fonction "example_list" :

fun main ()=
    _showconsole;
    _fooIList example_list 7 :: 5 :: 2 :: nil 10 EXAMPLE_PLUS;
    0;;

La console devrait afficher en retour : 17 : 15 : 12

Le même exemple mais en changeant de flag :

fun main ()=
    _showconsole;
    _fooIList example_list 7 :: 5 :: 2 :: nil 10 EXAMPLE_MOINS;
    0;;

La console devrait afficher en retour : -3: -5 : -8</p>

Documenter avec Doxygen

Commenter son code source, c'est bien, le documenter c'est encore mieux !

Il existe plusieurs outils. J'utilise (et les autres développeurs du langage
aussi) Doxygen, un outil complet et pratique.
Il permet d'exporter une documentation en plusieurs formats : du html au pdf en
passant par Latex et plusieurs autres. Si vous utilisez Code::Blocks, le plugin
est désormais intégré dans la barre de menu. Autrement, vous trouverez de l'information
à cette adresse : Wiki C::B.
Sous le nom de menu DoxyBlocks, vous configurez la sortie selon vos souhaits
et lancez la génération des documents via l'item "Extract documentation" (ou en
lançant "DoxyWizard" si vous désirez plus d'options avec l'assistant graphique). C'est
simple, rapide et efficace.
La documentation générée par cet outil est également incluse dans l'archive.

En amont, il est nécessaire de suivre une certaine syntaxe dans les commentaires.
Vous la trouverez dans le code source de l'archive téléchargeable, pour la plus
courante.Pour aller plus en profondeur, consultez le site de Doxygen.

Enfin, même si vous n'utilisez pas C::B, vous pouvez utiliser Doxygen pour générer
vos documentations !

Remarque : C::B intègre aussi un plugin pour Valgrind permettant de tester les
utilisations mémoire de votre application.

Updated by iri about 11 years ago · 1 revisions