Δημοσιεύτηκε: 14 Απρ 2012, 20:43
από migf1
Ξεκίνα με την υλοποίηση της εσωτερικής γραμμής εντολών, που θέλει δουλιτσα. Έναν parser που θα σκανάρει την εσωτερική γραμμή εντολών, θα ξεχωρίζει τις εντολές από τις παραμέτρους και θα τις αποθηκεύει με κάποιον δομημένο τρόπο, για να μπορέσει κατόπιν να τις εφαρμόσει.

Μπορείς ξανά να έχεις στην main() αρχικοποιημένο έναν πίνακα με όλες τις εντολές που θες να αναγνωρίζεις, μαζί με έξτρα πληροφορίες.

Ας πούμε για παράδειγμα πως θέλεις οι εντολές να έχουν και aliases (συνώνυμα), δηλαδή είτε σου γράψει "exit", είτε "quit", είτε "bye", κλπ να το εκλαμβάνεις ως εντολή εξόδου

Μπορείς επίσης στις έξτρα πληροφορίες της κάθε εντολής σου να έχεις αρχικοποιημένο τον μέγιστο αριθμό παραμέτρων που μπορεί να δεχτεί η συγκεκριμένη εντολή.

Δες ένα παράδειγμα κώδικα που χρησιμοποιεί πάνω κάτω την ίδια προσέγγιση με το AnimalTypes...

Μορφοποιημένος Κώδικας: Επιλογή όλων
#define MAX_CMD_ALIASES		4
#define MAX_CMD_PARAMS 2

enum ComTypeId {
CMDID_INVALID = -1,
CMDID_EXIT,
CMDID_HELP,
CMDID_LOOK,
CMDID_GO,
CMDID_SHOOT,
CMDID_GET,
CMDID_DROP,
MAX_COMMANDS
};

typedef struct CommandType {
enum ComTypeId id;
char *aliases[ MAX_CMD_ALIASES ]; /* array of char ptrs (μπορείς να το κάνεις στατικό πίνακα αν σε βολεύει καλύτερα) */
int nparams; /* */
char *params[ MAX_CMD_PARAMS ]; /* array of char ptrs (κι αυτό μπορείς να το κάνεις στατικό πίνακα) */
} CommandType;

...
int main( void )
{
CommandType cmdtable[ MAX_COMMANDS ] = {
/* .id .aliases .nparams .params */
{ CMDID_EXIT, {"exit", "x", "quit", "q"}, 0, {NULL, NULL} },
{ CMDID_HELP, {"help", "h", "?", NULL}, 0, {NULL, NULL} },
{ CMDID_LOOK, {"look", NULL, NULL, NULL}, 1, {NULL, NULL} },
{ CMDID_GO, {"go", "walk", NULL, NULL}, 0, {NULL, NULL} },
{ CMDID_SHOOT, {"shoot", "hit", NULL, NULL}, 1, {NULL, NULL} },
{ CMDID_GET, {"get", "pick", NULL, NULL}, 1, {NULL, NULL} },
{ CMDID_DROP, {"drop", NULL, NULL, NULL}, 2, {NULL, NULL} }
};

CommandType command = { CMDID_INVALID, {NULL, NULL, NULL, NULL}, 0, {NULL, NULL} };
...

Στην συνάρτηση του parser, θα περνάς π.χ. την stdin που σου έγραψε ο χρήστης, καθώς επίσης και την μεταβλητή command (by-reference). Θα σπας την stdin σε tokens, και θα ενημερώνεις κατάλληλα την μεταβλητή command.

Δηλαδή, μέσω μιας ακόμα συνάρτησης, ας πούμε...

Μορφοποιημένος Κώδικας: Επιλογή όλων
enum ComTypeId cmdtable_lookup_alias( const CommandType cmdtable[], const char *alias );

θα κοιτάς αν το 1ο token (τριμαρισμένο από κενά διαστήματα & τροποιημένο σε πεζά γράμματα) αντιστοιχεί στις εντολές που αναγνωρίζεις.

Προφανώς το κοίταγμα θα γίνεται πιάνοντας τον πίνακα εντολών από την αρχή του και ψάχνοντας για κάθε εντολή, την λίστα με τα aliases μέχρι να ταιριάξει κάποιο με το 1ο token (που στο παραπάνω πρότυπο είναι το όρισμα alias)
Αν βρεθεί, η συνάρτηση θα σου επιστρέφει...

Μορφοποιημένος Κώδικας: Επιλογή όλων
cmdtable[found].id (όπου found, η τιμή του μετρητή από το loop που σκανάρει τις γραμμές του πίνακα)

αλλιώς θα σου επιστρέφει CMDID_INVALID.

Βρισκόμαστε ακόμα στη συνάρτηση του parser... αν λοιπόν υπάρχει το 1ο token, θα κοπιάρεις μέσα στην command τη αντίστοιχη γραμμή του πίνακα...

Μορφοποιημένος Κώδικας: Επιλογή όλων
parser( char *input, Command *command )
{
...
ComTypeId try = cmdtable_lookup_alias( cmdtable, token1st );
if ( CMDID_INVALID != try )
memcpy( command, &cmdtable[ try ], sizeof(Command) );
...
}

Κατόπιν, με έγκυρο πλέον command , θα κοιτάς πόσες παραμέτρους περιμένει max, ώστε όσες έχουν δοθεί να τις περάσεις στο πεδίοparams της command.

Μορφοποιημένος Κώδικας: Επιλογή όλων
...
for (int i=0; i < command->nparams && more_stuff_in_stdin; i++ )
strncpy( command->params[ i ], next_token_from_stdin, MAXLEN_PARAM];
...

Με λίγα λόγια, όταν θα τελειώσει η parser() θα έχει βάλει μέσα στην μεταβλητή command το id της δοθείσας εντολής, καθώς και τις παραμέτρους που δόθηκαν (αν δόθηκαν... όσες δεν δόθηκαν μπορείς να τις έχεις ως NULL στο command.nparams.

Από εδώ και πέρα, μπορείς να χρησιμοποιήσεις την μεταβλητή command σε ξεχωριστές συναρτήσεις για τη διαχείριση της κάθε εντολή σου.

Για παράδειγμα...

Μορφοποιημένος Κώδικας: Επιλογή όλων
...
get_input( input );
parse_input( input, cmdtable, &command);

switch ( command.id )
{
case CMDID_LOOK:
do_cmd_lookup( command );
break;

case CMDID_HELP:
do_cmd_help( command );
break;
...
}

Μπορείς στον πίνακα των εντολών, να προσθέσεις άλλο ένα πεδίο που θα είναι δείκτης σε συνάρτηση, για κάθε εντολή δηλαδή θα έχεις μέσα στο struct της έναν δείκτη στη συνάρτηση που διαχειρίζεται την εντολή σου. Oπότε μόλις τελειώσει η parser() θα κάνεις μονάχα έναν έλεγχο να μην είναι command.id == CMDID_INVALID, κι όταν δεν είναι θα καλείς την συνάρτηση μέσα από την command...

Μορφοποιημένος Κώδικας: Επιλογή όλων
...
get_input( input );
parse_input( input, cmdtable, &command);

if ( CMDID_INVALID == command.id )
/* handle error here */
else
command.funcptr( command );

Αυτό προϋποθέτει όμως πως ΟΛΕΣ οι συναρτήσεις διαχείρισης εντολών παίρνουν ίδιο πλήθος & τύπο ορισμάτων, καθώς και ότι επιστρέφουν ίδιο τύπο. Κάτι που στο παραπάνω παράδειγμα ισχύει (επειδή το έκανα να ισχύει :lol: ).

Άρα...

Μορφοποιημένος Κώδικας: Επιλογή όλων
#define MAX_CMD_ALIASES		4
#define MAX_CMD_PARAMS 2

typedef struct CommandType {
enum ComTypeId id;
char *aliases[ MAX_CMD_ALIASES ]; /* array of char ptrs (μπορείς να το κάνεις στατικό πίνακα αν σε βολεύει καλύτερα) */
int nparams; /* */
char *params[ MAX_CMD_PARAMS ]; /* array of char ptrs (κι αυτό μπορείς να το κάνεις στατικό πίνακα) */
void (*funcptr)( struct CommandType command );
} CommandType;
...
int main( void )
{
CommandType cmdtable[ MAX_COMMANDS ] = {
/* .id .aliases .nparams .params */
{ CMDID_EXIT, {"exit", "x", "quit", "q"}, 0, {NULL, NULL}, &do_exit },
{ CMDID_HELP, {"help", "h", "?", NULL}, 0, {NULL, NULL} &do_help },
{ CMDID_LOOK, {"look", NULL, NULL, NULL}, 1, {NULL, NULL}, &do_look },
{ CMDID_GO, {"go", "walk", NULL, NULL}, 0, {NULL, NULL}, &do_go },
{ CMDID_SHOOT, {"shoot", "hit", NULL, NULL}, 1, {NULL, NULL}, &do_shoot },
{ CMDID_GET, {"get", "pick", NULL, NULL}, 1, {NULL, NULL}, &do_get },
{ CMDID_DROP, {"drop", NULL, NULL, NULL}, 2, {NULL, NULL}, &do_drop }
};

CommandType command = { CMDID_INVALID, {NULL, NULL, NULL, NULL}, 0, {NULL, NULL}, NULL };
...

Και προφανώς τις συναρτήσεις void do_xxx( Command comamnd ) υλοποιημένες και γνωστοποιημένα τα πρότυπά τους πριν την αρχικοποίηση του πίνακα ;)