I. Construction de l'interpréteur de commandes CI▲
I-A. Construire un environnement de développement pour la bibliothèque CLIB▲
.../clib/ed/inc
.../clib/ed/src
.../clib/ed/lib
I-B. Récupérer les fichiers▲
Dans …/inc :
- ansi.itm
- ascii.h
- assert.h
- bits.h
- ci.h
- ci_err.utm
- cnt.h
- cnt_err.itm
- fstr.h
- fstr_err.itm
- io.h
- pc_dbg.h
- str.h
- sys.h
- sysalloc.h
- tok.h
- tok_err.itm
- types.h
Dans …/src :
- ascii.c
- assert.c
- ci.c
- cnt.c
- fstr.c
- io.c
- str.c
- sysalloc.c
- tok.c
Les neuf fichiers sources servent éventuellement à générer la bibliothèque.
Bien sûr, on n’est pas obligé de générer la bibliothèque, on peut aussi ajouter ces fichiers au projet.
I-C. Créer la bibliothèque▲
Le chemin des fichiers inclus est :
.../clib
Le chemin des fichiers sources (.c) est :
.../clib/ed/src
Le chemin de la sortie est :
.../clib/ed/lib
La macro DBG_SYSALLOC doit être définie globalement. Typiquement (Borland C, gcc) :
... -DDBG_SYSALLOC
Générer la bibliothèque (les détails dépendent de l'implémentation).
I-D. Créer une application de test▲
Celle-ci est composée de trois fichiers. Le programme principal main.c et un ensemble de fonctions appelées par le shell (ainsi que le fichier d'interface qui va avec).
/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include "ed/inc/sysalloc.h"
#include "ed/inc/sys.h"
#include "ed/inc/io.h"
#include "app.h"
/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private variables =================================================== */
/* private functions =================================================== */
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
static
int
cb_out (
void
*
p_user, char
const
*
s)
{
int
err =
0
;
FILE *
fp =
p_user;
if
(
fp !=
NULL
)
{
fprintf (
fp, "
%s
"
, s);
fflush (
fp);
}
else
{
err =
1
;
}
return
err;
}
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
static
void
print_prompt (
void
)
{
printf (
"
\n
>
"
);
fflush (
stdout);
}
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
static
void
print_err (
ci_err_e err)
{
printf (
"
CI.ERR: %s
\n
"
, ci_serr (
err));
}
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
static
void
test (
void
)
{
ci_s ci;
static
ci_cfg_s const
a_cfg[] =
{
{
"
date
"
, date_man, date_cde}
,
{
"
time
"
, time_man, time_cde}
,
}
;
char
s_user[] =
"
User
"
;
ci_err_e err =
ci_init (&
ci, a_cfg, NELEM (
a_cfg));
if
(
err ==
CI_OK)
{
ci_install_out (&
ci, cb_out, stdout);
{
int
end =
0
;
while
(!
end)
{
print_prompt (
);
{
char
*
s_line =
get_line (
);
if
(
s_line !=
NULL
)
{
err =
ci_in (&
ci, s_line, s_user);
end =
err ==
CI_ERR_QUIT;
FREE (
s_line);
if
(
err &&
!
end)
{
print_err (
err);
}
}
else
{
end =
1
;
}
}
}
}
}
else
{
print_err (
err);
}
}
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
static
int
main_ (
void
)
{
test (
);
return
0
;
}
/* entry points ======================================================== */
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
int
main (
void
)
{
int
ret;
static
char
Trace[1
<<
8
];
SYS_INIT (
Trace, OFF);
ret =
main_ (
);
sys_mem_trace (
);
return
ret;
}
/* app.h */
#ifndef H_ED_APP_20050311102737
#define H_ED_APP_20050311102737
/* app.h */
#ifdef __cplusplus
extern
"
C
"
{
#endif
#include "ed/inc/ci.h"
/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* internal public data ================================================ */
/* internal public functions =========================================== */
/* entry points ======================================================== */
/* commands */
ci_cde_f date_cde;
ci_cde_f time_cde;
/* public data ========================================================= */
/* helps */
extern
char
const
date_man[];
extern
char
const
time_man[];
#ifdef __cplusplus
}
#endif
#endif /* guard */
/* Guards added by GUARD (c) ED 2000-2005 Feb 07 2005 Ver. 1.7 */
/* app.c */
/* app.c */
#include "app.h"
#include <stdio.h>
#include <time.h>
/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */
/* internal public data ================================================ */
/* internal public functions =========================================== */
/* entry points ======================================================== */
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
int
date_cde (
int
argc, char
const
**
argv, void
*
p_user)
{
int
ret =
0
;
if
(
argc ==
1
)
{
time_t now =
time (
NULL
);
struct
tm tm =
*
localtime (&
now);
char
s[64
];
char
const
*
s_user =
p_user ? p_user : ""
;
strftime (
s, sizeof
s, "
%A %d-%m-%Y (week %W)
"
, &
tm);
printf (
"
[%s] today is %s
\n
"
, s_user, s);
}
else
{
printf (
"
date change ...
\n
"
);
}
return
ret;
}
/* ---------------------------------------------------------------------
--------------------------------------------------------------------- */
int
time_cde (
int
argc, char
const
**
argv, void
*
p_user)
{
int
ret =
0
;
if
(
argc ==
1
)
{
time_t now =
time (
NULL
);
struct
tm tm =
*
localtime (&
now);
char
s[16
];
char
const
*
s_user =
p_user ? p_user : ""
;
strftime (
s, sizeof
s, "
%H:%M:%S
"
, &
tm);
printf (
"
%s
\n
"
, s);
printf (
"
[%s] it's now %s
\n
"
, s_user, s);
}
else
{
printf (
"
time change ...
\n
"
);
}
return
ret;
}
/* public data ========================================================= */
char
const
date_man[] =
{
"
[yyyy[ mm[ dd]]]
\n
"
}
;
char
const
time_man[] =
{
"
[hh[ mm[ ss]]]
\n
"
}
;
La macro DBG_SYSALLOC doit être définie globalement.
Une fois lancé, ce code produit :
SYSALLOC Overload (10 rec)
SYSALLOC Successful initialization: 10 records available
>
On peut ensuite passer des commandes :
SYSALLOC Overload (10 rec)
SYSALLOC Successful initialization: 10 records available
> help
command list
help quit date time
> date
[User] today is Friday 16-06-2006 (week 24)
> time
11:18:56
[User] it's now 11:18:56
> quit
SYSALLOC min=4294967295 max=4294967295 delta=0
SYSALLOC All-matched
SYSALLOC Released Memory
Press ENTER to continue.
II. Utilisation de l'interpréteur de commandes CI▲
II-A. Fonctionnement▲
Un interpréteur de commande est un mécanisme qui exécute une action en fonction d'une commande fournie sous forme textuelle. La ligne de commande est une chaîne de caractères qui peut comporter la commande suivie ou non de paramètres séparés par un espace.
"commande"
"commande parametre ..."
L'action est implémentée par un appel de fonction au format classique et universel :
int
action (
int
argc, char
**
argv);
Le principe est de définir une liste de commandes composée :
- du nom de la commande en minuscules ;
- d'une chaîne d'aide ;
- du nom de fonction associée.
static
ci_cfg_s const
a_cfg[] =
{
{
"
date
"
, date_man, date_cde}
,
{
"
time
"
, time_man, time_cde}
,
}
;
Le système a deux commandes internes préétablies : « quit » et « help ».
II-B. Restrictions▲
Les commandes préétablies ne sont pas configurables.
II-C. Construire une application pas à pas▲
II-C-1. Le code minimum▲
Ce code minimum montre comment on construit l'interpréteur de commande avec ses deux commandes intégrées 'help' et 'quit'. Dans un premier temps, il suffit de créer une structure ci_s et de l'initialiser avec ci_init() :
/* ci_01.c */
#include "ed/inc/ci.h"
#include <stdio.h>
/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
if (err != CI_OK)\
{\
fprintf (stderr,\
"ERR : %s(%d) at %s:%d\n"\
, ci_serr(err)\
, err\
, __FILE__\
, __LINE__\
);\
}\
}\
while (0)
int
main (
void
)
{
ci_s ci;
ci_err_e err;
err =
ci_init (&
, NULL
, 0
);
PERR
(
err);
return
0
;
}
Ce n'est pas très spectaculaire, mais ça fonctionne. Comme il n'y a pas de boucle, le programme s'arrête tout de suite. Normal.
On ajoute maintenant une application minimaliste comprenant une boucle applicative incluant :
- l'affichage d'un 'prompt' ;
- une lecture de ligne (saisie de la commande) ;
- un appel à la fonction de traitement de la commande.
/* ci_02.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include <stdio.h>
/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
if (err != CI_OK)\
{\
fprintf (stderr,\
"ERR : %s(%d) at %s:%d\n"\
, ci_serr(err)\
, err\
, __FILE__\
, __LINE__\
);\
}\
}\
while (0)
int
main (
void
)
{
ci_s ci;
ci_err_e err;
/* initialisation */
err =
ci_init (&
, NULL
, 0
);
PERR
(
err);
/* boucle applicative */
{
int
end =
0
;
do
{
/* prompt */
printf (
"
>
"
);
fflush (
stdout);
/* lecture d'une ligne */
{
char
*
line =
get_line
(
);
if
(
line !=
NULL
)
{
/* passage de la ligne à l'interpréteur de commande */
err =
ci_in
(&
ci, line, NULL
);
PERR
(
err);
/* detection de la fin */
end =
err ==
CI_ERR_QUIT;
}
free (
line), line =
NULL
;
}
}
while
(!
end);
}
return
0
;
}
Avec un petit exemple d'exécution :
> sdsdsd
> help
> quit
ERR : quit command(5) at ci_02.c:49
Press ENTER to continue.
On constate que la commande inconnue ne provoque pas de réaction, de même que la commande d'aide (help). C'est dû au fait que le module CI n'a pas, en interne, de fonction de sortie. C'est à l'utilisateur de fournir cette fonction au module. Ceci permet de répondre à tous les cas (la sortie ne se fait pas forcément sur stdout, elle peut se faire dans un port série, un socket etc.).
Nous allons donc compléter le code avec une fonction de sortie sur stdout :
/* ci_03.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include <stdio.h>
/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
if (err != CI_OK)\
{\
fprintf (stderr,\
"ERR : %s(%d) at %s:%d\n"\
, ci_serr(err)\
, err\
, __FILE__\
, __LINE__\
);\
}\
}\
while (0)
/* definition d'une fonction de sortie de type ci_out_f */
static
int
ci_out_cb (
void
*
p_user, char
const
*
s)
{
int
ret =
0
;
printf (
"
%s
"
, s);
fflush (
stdout);
return
ret;
}
int
main (
void
)
{
ci_s ci;
ci_err_e err;
/* initialisation */
err =
ci_init (&
ci, NULL
, 0
);
PERR
(
err);
/* installation de la fonction de sortie */
err =
ci_install_out (&
, ci_out_cb, NULL
);
PERR
(
err);
/* boucle applicative */
{
int
end =
0
;
do
{
printf (
"
>
"
);
fflush (
stdout);
{
char
*
line =
get_line
(
);
if
(
line !=
NULL
)
{
err =
ci_in
(&
ci, line, NULL
);
PERR
(
err);
end =
err ==
CI_ERR_QUIT;
}
free (
line), line =
NULL
;
}
}
while
(!
end);
}
return
0
;
}
Avec un petit exemple d'exécution :
> sdsdsd
> help
command list
help quit
> quit
ERR : quit command(5) at ci_03.c:58
Press ENTER to continue.
C'est terminé pour la partie 'minimale'. Nous allons ensuite aborder pas à pas le cas d'une application réelle.
II-C-2. Une application réelle▲
Nous allons définir une commande qui montre la version de l'application. Si on ajoute un paramètre 'lib', elle montre en plus la version des bibliothèques :
Commande
ver[ lib]
Paramètres
lib : optionnel
Comportement
Affiche la version de l'application
Le paramètre 'lib' provoque en plus l'affichage des bibliothèques.
Dans un premier temps, création d'une chaîne « mode d'emploi » ou « aide » qui reprend l'essentiel de la spécification de la commande…
/* définition du mode d'emploi de la commande 'ver'.*/
static
char
const
help_ver[] =
"
ver[ lib]
\n
"
;
puis création de la fonction de traitement. Un peu de code est placé dedans pour montrer le comportement des paramètres.
/* définition d'une fonction de traitement de commande de type ci_cde_f
pour traiter la commande 'ver'
*/
static
int
cde_ver_cb (
int
argc, char
const
**
argv, void
*
p_user)
{
int
ret =
0
;
/* quelques lignes pour monter le comportement des paramètres (debug) */
printf (
"
argc = %d
\n
"
, argc);
{
int
i;
for
(
i =
0
; i <
argc; i++
)
{
printf (
"
argv[%d] = '%s'
\n
"
, i, argv[i]);
}
}
return
ret;
}
Enfin, intégration de ces deux éléments dans le tableau de configuration. Son adresse et son nombre d'éléments sont alors passés à ci_init() à la place de NULL et 0 :
/* ci_04.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include "ed/inc/sys.h" /* NELEM */
#include <stdio.h>
#include <string.h>
/* version de l'application */
#define VER "0.4"
/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
if (err != CI_OK)\
{\
fprintf (stderr,\
"ERR : %s(%d) at %s:%d\n"\
, ci_serr(err)\
, err\
, __FILE__\
, __LINE__\
);\
}\
}\
while (0)
/* définition du mode d'emploi de la commande 'ver'.
La première ligne affiche automatiquement le nom de la commande.
On peut ajouter les paramètres et une ou des lignes
supplémentaires pour illustrer...
*/
static
char
const
help_ver[] =
"
[lib]
\n
"
"
Donne la version de l'application
\n
"
"
lib : liste aussi les versions des bibliothèques
\n
"
;
/* définition d'une fonction de traitement de commande de type ci_cde_f
pour traiter la commande 'ver'
*/
static
int
cde_ver_cb (
int
argc, char
const
**
argv, void
*
p_user)
{
int
ret =
0
;
/* quelques lignes pour montrer le comportement des paramètres (debug) */
printf (
"
argc = %d
\n
"
, argc);
{
int
i;
for
(
i =
0
; i <
argc; i++
)
{
printf (
"
argv[%d] = '%s'
\n
"
, i, argv[i]);
}
}
return
ret;
}
/* définition d'une fonction de sortie de type ci_out_f */
static
int
ci_out_cb (
void
*
p_user, char
const
*
s)
{
int
ret =
0
;
printf (
"
%s
"
, s);
fflush (
stdout);
return
ret;
}
int
main (
void
)
{
ci_s ci;
ci_err_e err;
/* tableau de structure de configuration (pour le moment, un seule commande) */
static
ci_cfg_s const
a_cfg[] =
{
{
"
ver
"
, help_ver, cde_ver_cb}
,
}
;
/* initialisation */
err =
ci_init (&
ci, a_cfg, NELEM
(
a_cfg));
PERR
(
err);
err =
ci_install_out (&
ci, ci_out_cb, NULL
);
PERR
(
err);
/* boucle applicative */
{
int
end =
0
;
do
{
printf (
"
>
"
);
fflush (
stdout);
{
char
*
line =
get_line
(
);
if
(
line !=
NULL
)
{
err =
ci_in
(&
ci, line, NULL
);
PERR
(
err);
end =
err ==
CI_ERR_QUIT;
}
free (
line), line =
NULL
;
}
}
while
(!
end)
;
}
return
0
;
}
Nous pouvons constater que la commande est maintenant prise en compte dans la liste :
> help
command list
help quit ver
>
Nous pouvons aussi demander une aide spécifique :
> help ver
USAGE ver [lib]
Donne la version de l'application
lib : liste aussi les versions des bibliothèques
>
Voici le résultat de quelques essais de commande 'ver' et de paramètres' :
> ver
argc = 1
argv[0] = 'ver'
> ver xx
argc = 2
argv[0] = 'ver'
argv[1] = 'xx'
> ver xx yy
argc = 3
argv[0] = 'ver'
argv[1] = 'xx'
argv[2] = 'yy'
>
D'autre part, maintenant qu'un fichier de configuration est installé, le module réagit de façon plus explicite à certaines erreurs :
> xxx
ERR : unknown command(8) at ci_04.c:95
>
ERR : no command(6) at ci_04.c:95
>
Nous allons maintenant modifier la fonction de traitement pour qu'elle fasse exactement ce qu'on attend d'elle :
/* ci_05.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include "ed/inc/sys.h" /* NELEM */
#include <stdio.h>
#include <string.h>
/* version de l'application */
#define VER "0.5"
/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
if (err != CI_OK)\
{\
fprintf (stderr,\
"ERR : %s(%d) at %s:%d\n"\
, ci_serr(err)\
, err\
, __FILE__\
, __LINE__\
);\
}\
}\
while (0)
/* définition du mode d'emploi de la commande 'ver'.
La première ligne affiche automatiquement le nom de la commande.
On peut ajouter les paramètres et une ou des lignes
supplémentaires pour illustrer...
*/
static
char
const
help_ver[] =
"
[lib]
\n
"
"
Donne la version de l'application
\n
"
"
lib : liste aussi les versions des bibliothèques
\n
"
;
/* définition d'une fonction de traitement de commande de type ci_cde_f
pour traiter la commande 'ver'
*/
static
int
cde_ver_cb (
int
argc, char
const
**
argv, void
*
p_user)
{
int
ret =
0
;
/* afficher la version de l'application */
printf (
"
VER = %s
\n
"
, VER);
/* si il y a un paramètre */
if
(
argc >
1
)
{
/* et que ce paramètre est 'lib' */
if
(
strcmp (
argv[1
], "
lib
"
) ==
0
)
{
printf (
"
bibliothèques :
\n
"
);
printf (
"
%-20s%s
\n
"
, ci_sid
(
), ci_sver
(
));
printf (
"
%-20s%s
\n
"
, io_sid
(
), io_sver
(
));
}
}
return
ret;
}
/* définition d'une fonction de sortie de type ci_out_f */
static
int
ci_out_cb (
void
*
p_user, char
const
*
s)
{
int
ret =
0
;
printf (
"
%s
"
, s);
fflush (
stdout);
return
ret;
}
int
main (
void
)
{
ci_s ci;
ci_err_e err;
/* tableau de structure de configuration (pour le moment, une seule commande) */
static
ci_cfg_s const
a_cfg[] =
{
{
"
ver
"
, help_ver, cde_ver_cb}
,
}
;
/* initialisation */
err =
ci_init (&
ci, a_cfg, NELEM
(
a_cfg));
PERR
(
err);
err =
ci_install_out (&
ci, ci_out_cb, NULL
);
PERR
(
err);
/* boucle applicative */
{
int
end =
0
;
do
{
printf (
"
>
"
);
fflush (
stdout);
{
char
*
line =
get_line
(
);
if
(
line !=
NULL
)
{
err =
ci_in
(&
ci, line, NULL
);
PERR
(
err);
end =
err ==
CI_ERR_QUIT;
}
free (
line), line =
NULL
;
}
}
while
(!
end)
;
}
return
0
;
}
Ce qui donne :
> ver
VER = 0.5
>
et :
> ver lib
VER = 0.5
bibliotheques :
CI 1.4
IO (c) ED 2003-2005 1.2
>
Le cas du paramètre inconnu n'est pas signalé. Une modification triviale est faisable. Il suffit d'ajouter le traitement adéquat dans la bonne branche. On en profite pour modifier le compte rendu en 1 pour signifier 'erreur de paramètre'.
/* ci_06.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include "ed/inc/sys.h" /* NELEM */
#include <stdio.h>
#include <string.h>
/* version de l'application */
#define VER "0.6"
/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
if (err != CI_OK)\
{\
fprintf (stderr,\
"ERR : %s(%d) at %s:%d\n"\
, ci_serr(err)\
, err\
, __FILE__\
, __LINE__\
);\
}\
}\
while (0)
/* définition du mode d'emploi de la commande 'ver'.
La première ligne affiche automatiquement le nom de la commande.
On peut ajouter les paramètres et une ou des lignes
supplémentaires pour illustrer...
*/
static
char
const
help_ver[] =
"
[lib]
\n
"
"
Donne la version de l'application
\n
"
"
lib : liste aussi les versions des bibliothèques
\n
"
;
/* définition d'une fonction de traitement de commande de type ci_cde_f
pour traiter la commande 'ver'
*/
static
int
cde_ver_cb (
int
argc, char
const
**
argv, void
*
p_user)
{
int
ret =
0
;
/* afficher la version de l'application */
printf (
"
VER = %s
\n
"
, VER);
/* si il y a un paramètre */
if
(
argc >
1
)
{
/* et que ce paramètre est 'lib' */
if
(
strcmp (
argv[1
], "
lib
"
) ==
0
)
{
printf (
"
bibliothèques :
\n
"
);
printf (
"
%-20s%s
\n
"
, ci_sid
(
), ci_sver
(
));
printf (
"
%-20s%s
\n
"
, io_sid
(
), io_sver
(
));
}
else
{
printf (
"
parametre inconnu
\n
"
);
ret =
1
;
}
}
return
ret;
}
/* définition d'une fonction de sortie de type ci_out_f */
static
int
ci_out_cb (
void
*
p_user, char
const
*
s)
{
int
ret =
0
;
printf (
"
%s
"
, s);
fflush (
stdout);
return
ret;
}
int
main (
void
)
{
ci_s ci;
ci_err_e err;
/* tableau de structure de configuration (pour le moment, un seule commande) */
static
ci_cfg_s const
a_cfg[] =
{
{
"
ver
"
, help_ver, cde_ver_cb}
,
}
;
/* initialisation */
err =
ci_init (&
ci, a_cfg, NELEM
(
a_cfg));
PERR
(
err);
err =
ci_install_out (&
ci, ci_out_cb, NULL
);
PERR
(
err);
/* boucle applicative */
{
int
end =
0
;
do
{
printf (
"
>
"
);
fflush (
stdout);
{
char
*
line =
get_line
(
);
if
(
line !=
NULL
)
{
err =
ci_in
(&
ci, line, NULL
);
PERR
(
err);
end =
err ==
CI_ERR_QUIT;
}
free (
line), line =
NULL
;
}
}
while
(!
end)
;
}
return
0
;
}
Ce qui donne :
> ver xxx
VER = 0.6
parametre inconnu
ERR : command parameter error(7) at ci_06.c:111
>