Développer un shell de base

Résolu
sb42student Messages postés 5 Date d'inscription vendredi 10 avril 2015 Statut Membre Dernière intervention 5 mai 2015 - Modifié par sb42student le 10/04/2015 à 19:07
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 - 5 mai 2015 à 22:22
Bonjour, je souhaite plus ou moins dans le cadre d'un projet scolaire développer un shell en C.

J'aimerai des critiques sur mes idées :) et pourquoi pas quelques pistes ^^

Alors le cahier des charges:

- exécutable sur MAC YOSEMITE
- affiche un prompt
- permet l'exécution des binaires présent sur la machine (ls / clear / grep ... etc)
- permet l'exécution de certain builtin (au moins cd et env ^^)
- gère les redirections entrée/sortie des commandes et les séparateurs (<>|<<>>&;&&)
- gère une liste des commandes déjà exécutées (flèche haut/bas) et l'édition dynamique de la ligne de commandes (flèche gauche/droite retour arr ...)
- C'est déjà bien pour le début ^^

- fonctions autorisées (sinon c'est pas drôle) :
- malloc
- free
- write
- read
- open
- close
- readdir
- stat/lstat
- tcsetattr
- tcgetattr
- fork
- waitpid
- dup
- pipe
- execve
- exit
- j'ai plus la liste sous les yeux mais il me semble que c'est tout ^^

Bon ça fait un peu de boulot, tout n'est pas encore clair dans ma tête.
Surtout au niveau de la gestion du pipe.

Grosso modo je pense partir sur qqc du genre:

--MAIN--

- modification des paramètres du shell, en mode non Canonique sans Echo
pour l'édition dynamique de la ligne de commandes et la gestion de la liste
des commande passées // (tcgetattr et tcsetattr)

- création d'un arbre (je pense) trié par ordre alphabétique contenant les
couples [nom des binaires : path des binaires] // (open / readdir dans "/bin",
"/user/bin")

- boucle infini
- affichage du prompt
- lecture (read sur fd=0) jusqu'au '\n'
- interception de touches spéciale (flèche/retour arr ...)
- construction/modification de la chaîne (ligne de commande) à afficher
- affichage de la ligne de commande en cours
- enregistrement de la ligne de commandes (pour la liste)
- analyse de la ligne (identification de binaire/builtin, arguments,
redirecteur, séparateur, Erreurs ...) certainement via la création d'une chaîne
de token à passer à un parser...
- exécution des binaires/builtin avec gestion des entrées/sorties // (fork /
waitpid / dup / pipe)
- retour à la boucle

- Destruction des variables avec free (arbre, liste ...)

- restauration des paramètres du shell

--END--

Alors avant de s'attaquer à chaque point, au niveau de la construction du programme, vous en pensez quoi??

Merci beaucoup à ceux qui auront pris le temps de lire ça :)
A voir également:

7 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 5/05/2015 à 22:23
Salut.

Comme déjà dit, si tu mélanges lexer et parser, tu ne vas pas t'en sortir ! Un lexer est tout simple, se code en 2-3h environ (pour un non débutant, je te rassure) et ne fait pas de grammaire. Il ne fait qu'extraire des tokens selon des règles arbitraires. La difficulté viendra avec le parser qui, lui effectivement, devra gérer les "forêts" de if, la priorité entre "||" et "&&", etc...
Pour faire propre, normalement, on fait un tableau contenant des structures avec la description de tout ce qui est un token. Exemple: "for", "if", "$((", ")", "{", etc...
Ce tableau est appliqué lorsque l'on recherche un token dans une chaîne. C'est ultra générique, et pour gérer de nouveaux tokens, il suffit juste de l'ajouter à ton tableau ;). Il y a quelques exceptions à faire pour gérer escaping et "équilibre" des parenthèses (ou bracket, ou curly bracket, etc... tout ce qui va par deux).

Plutôt qu'une liste chaînée, j'aurais fait un simple tableau dynamique: plus facile car accès "direct", plus performant car localité de la data (c'est un peu avancé pour que je t'embrouille avec), et moins d'allocations. Bien évidemment, un tableau dynamique en C, ça n'existe pas, mais ça se code ! (Le principe est d'avoir une taille et une capacité, la capacité est toujours plus grand que la taille. Si ta taille dépasse ta capacité, tu doubles la capacité. Tu as alors O(log(n)) allocations vs O(n) allocations avec ta liste chaînée. Tu as du "gâchis" car tu as souvent des "trous" en fin de tableau, mais c'est plus performant. Si tu ne te sens pas prêt de coder ça, pas de souci, tu peux utiliser une liste chaînée (je trouve juste plus facile d'utiliser et de débugger un tableau, plutôt qu'une liste). Dans le parser, tu auras parfois besoin de "regarder" plus loin que 1 case après toi, c'est là que le tableau est pratique (mais ça se fait tout aussi bien avec une liste).

Pour t'aiguiller je te fais un mini exemple, qui ne gère pas encore tout (à toi de le compléter ou t'en inspirer) mais illustre ce que j'ai expliqué plus haut:
#include <stdio.h>
#include <string.h>

typedef unsigned char bool; /* Real type */
typedef unsigned int uint32_t; /* Real type, normally in <stdint.h>, a good practice to use it */

typedef enum
  {
    TOKEN_EAT,
    TOKEN_DQ,
    TOKEN_SQ,
    TOKEN_SEMI,
    TOKEN_DBL_SEMI,
    TOKEN_OPERATOR,
    TOKEN_NEG,
    TOKEN_DBLAND,
    TOKEN_DBLOR,
    TOKEN_PIPE,
    TOKEN_OP_ARITHM,
    TOKEN_AND,
    TOKEN_OP_BRA,
    TOKEN_CL_BRA,
    TOKEN_OP_PAR,
    TOKEN_CL_PAR,
    TOKEN_REDIR,
    TOKEN_ASS_WORD,
    TOKEN_OP_CMD,
    TOKEN_OP_CURLY,
    TOKEN_CL_CURLY,
    TOKEN_EQUAL,
    TOKEN_STRING,
    TOKEN_NAME,
    TOKEN_FOR,
    TOKEN_WHILE,
    TOKEN_NULL
  } e_token_type;

typedef struct
{
  const char* op;
  uint32_t size;
  e_token_type type;
} t_oplist;

typedef struct
{
  char content[128]; /* Replace with a dynamic array */
  uint32_t size;
  e_token_type type;
} t_lexer_token;

typedef struct
{
  t_lexer_token tokens[2048];  /* Replace with a dynamic array */
  uint32_t size;
} t_lexer;

static const t_oplist existing_token[] =
  {
    {"while", 5, TOKEN_WHILE}, /* Sort by size, for obvious reason ! */
    {"$((", 3, TOKEN_OP_ARITHM},
    {">>-", 3, TOKEN_REDIR},
    {"for", 3, TOKEN_FOR},
    {"$(", 2, TOKEN_OP_CMD},
    {"${", 2, TOKEN_OP_CURLY},
    {">>", 2, TOKEN_REDIR},
    {"<<", 2, TOKEN_REDIR},
    {"||", 2, TOKEN_DBLOR},
    {">|", 2, TOKEN_REDIR},
    {"<>", 2, TOKEN_REDIR},
    {"<&", 2, TOKEN_REDIR},
    {">&", 2, TOKEN_REDIR},
    {"&&", 2, TOKEN_DBLAND},
    {"{ ", 2, TOKEN_OP_BRA},
    {"{\n", 2, TOKEN_OP_BRA},
    {"{\v", 2, TOKEN_OP_BRA},
    {"{\t", 2, TOKEN_OP_BRA},
    {"{\r", 2, TOKEN_OP_BRA},
    {"{\f", 2, TOKEN_OP_BRA},
    {" {", 2, TOKEN_CL_BRA},
    {"\n{", 2, TOKEN_CL_BRA},
    {"\v{", 2, TOKEN_CL_BRA},
    {"\t{", 2, TOKEN_CL_BRA},
    {"\r{", 2, TOKEN_CL_BRA},
    {"\f{", 2, TOKEN_CL_BRA},
    {"))", 2, TOKEN_TOKEN},
    {";;", 2, TOKEN_DBL_SEMI},
    {"! ", 2, TOKEN_NEG},
    {"}", 1, TOKEN_CL_BRA},
    {"|", 1, TOKEN_PIPE},
    {"&", 1, TOKEN_AND},
    {"(", 1, TOKEN_OP_PAR},
    {")", 1, TOKEN_CL_PAR},
    {">", 1, TOKEN_REDIR},
    {"<", 1, TOKEN_REDIR},
    {"(", 1, TOKEN_OP_PAR},
    {")", 1, TOKEN_CL_PAR},
    {"{", 1, TOKEN_OP_CURLY},
    {"}", 1, TOKEN_CL_CURLY},
    {";", 1, TOKEN_SEMI},
    {" ", 1, TOKEN_EAT},
    {"\n", 1, TOKEN_EAT},
    {"\v", 1, TOKEN_EAT},
    {"\t", 1, TOKEN_EAT},
    {"\r", 1, TOKEN_EAT},
    {"\f", 1, TOKEN_EAT},
    {"=", 1, TOKEN_EQUAL},
    {NULL, 1, 0}
  };

void addToLexer(t_lexer* lexer, const char* text, uint32_t text_size, e_token_type type)
{
  t_lexer_token item;
  strncpy(item.content, text, text_size);
  item.content[text_size] = 0;
  item.size = text_size;
  item.type = type;
  lexer->tokens[lexer->size] = item;
  ++lexer->size;
}

t_oplist searchTokenType(const char* s)
{
  const t_oplist* ex_tok = existing_token;
  t_oplist not_found = {0, 0, 0};

  while (ex_tok && ex_tok->op)
  {
    if (strncmp(s, ex_tok->op, ex_tok->size) == 0)
      return *ex_tok;
    ++ex_tok;
  }

  return not_found;
}

bool fillLexerFromString(const char* s, t_lexer* lexer)
{
  t_oplist current;
  const char* prev = s; /* Previous time we encountered a known token. */

  while (s && *s)
  {
    /* Handle \\ escaping */
    if (*s == '\\')
    {
      ++s;
      continue;
    }

    /* Search any know token */
    current = searchTokenType(s);
    /*
       if current.type == TOKEN_OP_*
       add +1 a counter !
       if current.type == TOKEN_CL_*
       dec -1 a counter !
       At the end, check if the counter is equal to 0.
       If not, there is a syntax error!
    */

    /* Find another token ? let's see if we have something inbetween */
    if ((current.op != 0 || *s == '"' || *s == '\'') && prev != s)
      addToLexer(lexer, prev, s - prev, TOKEN_NAME);

    /* Token found */
    if (current.op != 0)
    {
      s += current.size;
      if (current.type != TOKEN_EAT)
        addToLexer(lexer, current.op, current.size, current.type);
      prev = s;
    }
    /* Token not found, but could be an escaped string */
    else if (*s == '"' || *s == '\'')
    {
      /* handleEscaping(); Handle it for real */
      ++s;
      while (*s && *s != '\'' && *s != '"') /* <- dumb function */
        ++s;
      if (!*s || (*s != '\'' && *s != '"'))
      {
        /* string not finished! */
        return 0;
      }
      ++s;
    }
    else
      ++s;
  }

  /* Handle what's remain at the end of the input */
  if (prev != s)
    addToLexer(lexer, prev, s - prev, TOKEN_NAME);

  return 1;
}

void print(const t_lexer* lexer)
{
  uint32_t i = 0;

  for (i = 0; i < lexer->size; ++i)
    printf("<%s (%i)> ", lexer->tokens[i].content, lexer->tokens[i].type);
  printf("\n");
}

int main()
{
  const char* cmd = "for i in seq 1 10 20; do echo toto &&echo titi\n||read \"hello &&   escape\"; done";

  t_lexer lexer;
  lexer.size = 0;
  if (!fillLexerFromString(cmd, &lexer))
    printf("Syntax error !\n");

  print(&lexer);

  return 0;
}


Tu remarqueras qu'on ne sépare pas par "espace" mais par unité logique. Tout ce qui est entre deux tokens (voir la liste des tokens), est alors du "texte" (même si plus tard on s'aperçoit que c'est une variable, une fonction ou une commande. Je dégage tout ce qui est espace, mais attention certaines règles ont besoin d'un espace ou d'un saut de ligne, c'est le cas de "{ }" lorsque tu fais un bloc de fonction qui demande à commencer par un espace ou un ";". Il suffit de faire plusieurs version du token ("{\n", "{ ", etc...).
J'ai gérer l'escaping de manière naïve. Dans l'idéal, il faudrait remplacer ça par une fonction "intelligente" (j'entends qui ne mélange pas " et ', qui ne se fait pas avoir par: "to\"to" (qui vaut to"to), etc...).

Si tu as des questions (ou si mon anglais est trop pourri :p), n'hésite pas.

__________________________________________________________________________________________________

Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
3
sb42student Messages postés 5 Date d'inscription vendredi 10 avril 2015 Statut Membre Dernière intervention 5 mai 2015
10 avril 2015 à 19:24
Bon ça manque quand même de code pour un premier post ^^

Allé on commence par le début:

Initialisation et restauration des paramètres du shell.

SH42.H


#ifndef sh42_H
# define sh42_H

# include "libft.h" // Contient des fonctions de la libc

# include <termios.h>

int sh_SetTermSettings(struct termios *attributes);
int sh_ResetTermSettings(struct termios *attributes);
void sh_ExecShell42(int ac, char **av, char **ep);

#endif


MAIN.C


#include "sh42.h"

int sh_SetTermSettings(struct termios *attributes)
{
if (tcgetattr(0, attributes) == -1)
return (-1);
attributes->c_lflag &= ~ICANON;
attributes->c_lflag &= ~ECHO;
attributes->c_cc[VMIN] = 1; //On récupère la main dès que au moins 1 caractère est lu par read
attributes->c_cc[VTIME] = 0;
if (tcsetattr(0, TCSADRAIN, attributes) == -1)
return (-1);
return (0);
}

int sh_ResetTermSettings(struct termios *attributes)
{
if (tcgetattr(0, attributes) == -1)
return (-1);
attributes->c_lflag |= ICANON;
attributes->c_lflag |= ECHO;
if (tcsetattr(0, TCSADRAIN, attributes) == -1)
return (-1);
return (0);
}

int main(int ac, char **av, char **ep)
{
struct termios attributes;

if (sh_SetTermSettings(&attributes) == -1)
return (-1);
// sh_ExecShell42(ac, av, ep); //A coder! ^^
if (sh_ResetTermSettings(&attributes) == -1)
return (-1);
return (0);
}


Des questions? Des remarques?

Merci!
0
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 11/04/2015 à 13:34
Bonjour.

Vu qu'il n'y a pas vaiment de question, il n'y aura pas vraiment de réponse :p.
Je vais essayer de te mettre quelques conseils:

- Commence par le lexer (ce qui lit les caractères et mots d'une commande, et gère "l'escaping"). Les mots "lexés" iront dans un tableau.
- Fait une procédure de débug qui t'affiche les mots "lexés" (pratique pour tester l'escaping).
- Réalise le plus gros du travail: le parser. Utilise un AST. Tu lis ton tableau de lexing, et via la grammaire tu construis l'arbre équivalent.
- Fait une procédure de débug qui t'affiche l'arbre de lexing. Je te conseille de l'écrire au format .dot, ainsi tu pourras demander à dotty de te dessiner l'arbre super facilement. Tu peux aussi faire une procédure d'affichage standard peu évoluée pour débugger vite. Je te conseille les deux. En plus afficher un ast via dotty, c'est la classe et ça te fait du bonus pour pas cher ^^.
- Une fois ces parties de faite, il ne te reste qu'à réaliser une procédure d'exécution, qui va parcourir ton arbre. Ce n'est pas le plus dur, car le parcours d'arbre se fait séquentiellement. Tu n'as qu'à "enchaîner" les ordres. En revanche, c'est une partie un peu plus "technique", puisqu'il te faudra utiliser des forks, des dup, des execve, du waitpid en cas de subshell, etc...

Ne t'embête pas avec termios au début (tu le feras plus tard). Emule la commande -c de bash, qui te permet d'exécuter une commande, ou lit la à partir d'un fichier. Il te faut surtout lire à partir d'un fichier afin de réaliser un jeu de test. C'est indispensable. Sinon, en ajoutant des fonctionnalités, tu risques de ne pas te rendre compte que tu casses potentiellement autre chose. Un gros jeu de tests est nécessaire.

Bonne chance.

__________________________________________________________________________________________________

Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
sb42student Messages postés 5 Date d'inscription vendredi 10 avril 2015 Statut Membre Dernière intervention 5 mai 2015
Modifié par sb42student le 11/04/2015 à 18:56
Hey! Merci Cptpingu!

Alors ouais il n'y a pas vraiment de question, c'est plus pour partager mes idées et avoir vos avis :)
Ton intervention est exactement le genre de réponse que j'attendais ;)

Alors:

- Commence par le lexer (ce qui lit les caractères et mots d'une commande, et gère "l'escaping"). Les mots "lexés" iront dans un tableau.

Une genre d'strsplit, je zappe les blancs et les tabulations et je renvoi un tableau de char* . Je pensai commencer par ça en effet!

- Réalise le plus gros du travail: le parser. Utilise un AST. Tu lis ton tableau de lexing, et via la grammaire tu construis l'arbre équivalent.

Alors là.. ^^ Je vais me pencher sur les arbres syntaxiques. J'avoue que j'essayais de m'en passer. Mais ce sera certainement bien plus propre que ma chaîne de token et son interprétation.. Je bouquine et je reviens.

- Une fois ces parties de faite, il ne te reste qu'à réaliser une procédure d'exécution, qui va parcourir ton arbre. Ce n'est pas le plus dur, car le parcours d'arbre se fait séquentiellement. Tu n'as qu'à "enchaîner" les ordres. En revanche, c'est une partie un peu plus "technique", puisqu'il te faudra utiliser des forks, des dup, des execve, du waitpid en cas de subshell, etc...

Oui, ça je m'y suis déjà un peu penché (gestion père/fils avec fork, execve et waitpid). Pas l'air très complexe mais faut encore que je bouquine de coté de dup/dup2/pipe pour les redirections :)

-Ne t'embête pas avec termios au début (tu le feras plus tard). Emule la commande -c de bash, qui te permet d'exécuter une commande, ou lit la à partir d'un fichier. Il te faut surtout lire à partir d'un fichier afin de réaliser un jeu de test. C'est indispensable.

Ouep! Termios c'était pour se mettre en jambe vite fait ^^ se sera vite commenté dès les premiers tests possibles. Pour la lecture à partir d'un fichier, on est d'accord c'est genre pour mettre tout un tas de commandes et pouvoir tester mes retour VS ceux d'un shell fonctionnel??

- Bonne chance.

Merci!! ;) Je reviens ce soir pour mettre un peu de code :)

++ à tous!
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
Modifié par cptpingu le 12/04/2015 à 17:39
Une genre d'strsplit, je zappe les blancs et les tabulations et je renvoi un tableau de char* . Je pensai commencer par ça en effet!


Oula non pas tout fait :p. Un lexer c'est plus avancé que ça. Ca découpe une chaîne en unité logique. Ca te dit aussi s'il y a erreur de syntaxe. Ca gère l'escaping, et ça fait disparaître l'inutile (espace, saut de ligne, commentaire). Je te déconseille d'utiliser un strtok ou équivalent. Il faut plutôt gérer ça à la "main", via des "états" (pendant que tu avances dans ta chaîne, il te faut savoir si tu es au milieu d'un commentaire, d'un texte échappé, etc...).

Ex:
ls -a && echo "je fais un \"ls\" -a (youpi)" || (echo "fail" | cat -A) & # un commentaire


Donnera:
ls
-a
&&
echo
"je fais un \"ls\" -a (youpi)"
||
(
echo
"fail"
|
cat
-A
)
&


Tu noteras qu'on a géré l'escaping, qu'on a bien vu que "&&" ce n'était pas deux "&" (pareil pour || et |).

Il ne faut pas passer à l'étape de parsing, si le lexing foire.
Exemple:
"une erreur conne
'une autre erreur hihi
une erreur conne"
une autre erreur hihi'
toto \

Tout ceci, sont des exemples d'erreurs de syntaxe, qu'il faut détecter à cette étape.

Le parsing te donnera des erreurs de grammaire (exemple: "toto && && toto").


Alors là.. ^^ Je vais me pencher sur les arbres syntaxiques. J'avoue que j'essayais de m'en passer. Mais ce sera certainement bien plus propre que ma chaîne de token et son interprétation.. Je bouquine et je reviens.


Je te donne mon expérience: J'ai fait la même erreur que toi à mon époque (2007 eviron ^^). J'avais la flemme de faire un arbre, et je me suis dit: "bon aller, je parse dans un tableau à l'arrache, ça ira plus vite". Grosse erreur ! Gérer des &&, des || et des () imbriqués c'est l'enfer sans arbre. Au début c'est plus simple, mais pour des cas un peu "gros", ça ne passe plus du tout. Je l'ai vite regretté, et au final, j'ai du recommencer :(. Je comprends que de premier abord, ça paraisse plus rapide, et qu'apprendre à faire un AST, ça paraisse chiant. Mais crois moi, le temps "perdu" à apprendre à faire de l'AST (qui sont juste des arbres généraux tout facile), tu feras gagner énormément de temps.
Je te conseille de faire une classe Node qui contient un tableau de noeud, un type, et sa valeur. Les fonctions de créations de noeud retournent un booléen qui dit si la création a réussi à une étape donnée. Elle prendront en argument le lexer, la position dans le lexer, et un arbre (un Node** car on va ajouter des choses à notre Node*, donc au lancement de la méthode, tu feras un truc du genre: Node* parser; parse(lexer, 0, &parser); le parser se fera compléter par référence). J'ai aussi fait l'erreur, au début du parsing de renvoyer le nouvel arbre de parse. Et je n'avais plus aucun moyen de retourner s'il y avait une erreur de grammaire. Raison pour laquelle je te conseille ce design.


Pour la lecture à partir d'un fichier, on est d'accord c'est genre pour mettre tout un tas de commandes et pouvoir tester mes retour VS ceux d'un shell fonctionnel??


Oui, il faut que tu codes en "Test Driven". C-à-d, que tu codes tes tests d'abord, et ensuite que tu codes tes étapes. Les exemples et tests doivent te faire avancer, et pas le contraire. Sinon, tu auras la flemme de les faire, et tu perdras du temps, et de la robustesse (tu ne verras pas les regressions accidentelles). Autre erreur que j'avais faite, et encore regretté :p.

__________________________________________________________________________________________________

Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
0
sb42student Messages postés 5 Date d'inscription vendredi 10 avril 2015 Statut Membre Dernière intervention 5 mai 2015
12 avril 2015 à 13:54
Pfouaa!! Merci beaucoup cptpingu!!!

Ok pour le lexer! Je vais essayer de construire ça tout de suite :)

Pour le TDD tu as tout à fait raison! C'est un débat qui revient regulièrement dans mon école, et c'est vrai que le principal problème de la plupart des dev en herbe c'est la flemme de faire des test en cours de route ^^

J'ai vraiment envi de faire qqc de propre pour ce projet, donc chaud patate pour l'AST!

Je reviens quand le lexer tient debout ;)

Et encore un grand merci! J'ai hésité à faire ce post, mais je sens que je vais gagner un temps fou!!

++ à tous!
0
sb42student Messages postés 5 Date d'inscription vendredi 10 avril 2015 Statut Membre Dernière intervention 5 mai 2015
Modifié par sb42student le 5/05/2015 à 04:50
Alors le lexer prend forme mais j'arrive pas à faire qqc de complet et de propre ^^
Surtout au niveau de l'implementation des règles de changement d'état. J'ai pas trop regardé ce qui se faisait déjà.. J'attend les avis :) si vous pensez que c'est vraiment trop "dégueulasse" j'irais voir.

Pour le moment je ne gère pas les parenthèse (je vais pas partir tout de suite sur l'interpretation de shell script) ni le séparateur "||" mais celui là il faudra que je trouve un moyen de l'avoir ^^

Je pense que le reste est fonctionnel, pour le moment je teste juste avec l'argv, je fais le main pour la lecture depuis un fichier demain matin.

Deux choses me gène énormément pour le moment :
-les fonctions qui gèrent les règles (forêts de if/if else....)
-ma gestion de l'index... (une ternaire dans la condition du while qui me permet de passer une dernière fois dans la boucle avec str[i] == '\0')

Bon le code maintenant. N'hésitez pas à être dur! J'aimerai vraiment qu'il soit propre mais je suis pas vraiment au point avec les règles de "bonne conduite" ^^

LEXER.H

#ifndef LEXER_H
# define LEXER_H

# define INDEX ((i == 0) ? i : (i - 1))
# define RULES 6
# define ESCAPMENT "\\"
# define STANDARD " \t\n"
# define FRAME "\"\'"
# define SEPARATOR ";&"
# define REDIRECTOR "<>|"

# include "libft.h"

typedef enum e_vstate
{
InSynError = -2,
InLexError = -1,
Standard,
InEscapment,
InFrame,
InIdentifier,
InSeparator,
InRedirector
} t_vstate;

typedef enum e_type
{
None,
Escapment,
Frame,
Identifier,
Separator,
Redirector
} t_type;

typedef struct s_state
{
t_vstate old;
t_vstate curr;
} t_state;

typedef struct s_token
{
t_type type;
char *value;
} t_token;

typedef struct s_lexer
{
t_list *tokenlst;
int err;
int err_index;
t_type err_type;
} t_lexer;

typedef t_vstate (*t_func)(char *, int);

t_lexer *str_lex(char *str);
int maj_tokenlst(t_list **atokenlst, t_state state, char *str, int i);

void init_rules_tab(t_func *rules_tab);
void destroy_tokenlst(t_list **atokenlst);
void destroy_lexed(t_lexer **lexed);

t_vstate rules_standard(char *str, int i);
t_vstate rules_frame(char *str, int i);
t_vstate rules_escapment(char *str, int i);
t_vstate rules_identifier(char *str, int i);
t_vstate rules_separator(char *str, int i);
t_vstate rules_redirector(char *str, int i);

#endif

LEXER.H
LEXER.C

#include "lexer.h"

static t_lexer *get_return(t_list *tokenlst, t_state state, int i)
{
t_lexer *lexed;

if (tokenlst == NULL)
return (NULL);
lexed = (t_lexer *)malloc(sizeof(t_lexer));
if (lexed == NULL)
{
destroy_tokenlst(&tokenlst);
return (NULL);
}
lexed->tokenlst = tokenlst;
if (state.curr >= Standard)
{
lexed->err = None;
lexed->err_index = -1;
lexed->err_type = None;
}
else
{
lexed->err = state.curr;
lexed->err_index = i;
lexed->err_type = (t_type)state.old;
}
return (lexed);
}

static t_list *get_token(t_type type, char *str, int i, int len)
{
t_list *token;
t_token data;

data.type = type;
data.value = ft_strsub(str, i, len);
if (data.value == NULL)
return (NULL);
else
{
token = ft_lstnew((void *)&data, sizeof(t_token));
if (token == NULL)
free(data.value);
}
return (token);
}

int maj_tokenlst(t_list **atokenlst, t_state state, char *str, int i)
{
static int len = 1;
t_list *token;

if (state.old == state.curr && str[i] != '\0')
len++;
else
{
token = get_token((t_type)state.old, str, (i - len), len);
if (token == NULL)
{
destroy_tokenlst(atokenlst);
return (0);
}
ft_lstadd(atokenlst, token);
len = 1;
}
return (1);
}

t_lexer *str_lex(char *str)
{
t_func rules_tab[RULES];
t_list *tokenlst;
t_state state;
int i;
int ret;

init_rules_tab(rules_tab);
tokenlst = NULL;
state.old = Standard;
i = 0;
ret = 1;
while (str[INDEX] != '\0')
{
state.curr = rules_tab[state.old](str, i);
if (state.old != Standard)
ret = maj_tokenlst(&tokenlst, state, str, i);
if (ret == 0 || state.curr < Standard)
return (get_return(tokenlst, state, i));
state.old = state.curr;
i++;
}
return (get_return(tokenlst, state, i));
}

LEXER.C
EXTERN.C

#include "lexer.h"

void init_rules_tab(t_func *rules_tab)
{
rules_tab[Standard] = rules_standard;
rules_tab[InFrame] = rules_frame;
rules_tab[InEscapment] = rules_escapment;
rules_tab[InIdentifier] = rules_identifier;
rules_tab[InSeparator] = rules_separator;
rules_tab[InRedirector] = rules_redirector;
}

void destroy_tokenlst(t_list **atokenlst)
{
t_list *tmp;

while (*atokenlst != NULL)
{
tmp = (*atokenlst)->next;
free(((t_token *)(*atokenlst)->content)->value);
((t_token *)(*atokenlst)->content)->value = NULL;
free((*atokenlst)->content);
(*atokenlst)->content = NULL;
free(*atokenlst);
(*atokenlst) = tmp;
}
}

void destroy_lexed(t_lexer **alexed)
{
destroy_tokenlst(&((*alexed)->tokenlst));
free(*alexed);
(*alexed) = NULL;
}
EXTERN.C
RULES.C

#include "lexer.h"

t_vstate rules_standard(char *str, int i)
{
if (ft_strchr(STANDARD, str[i]))
return (Standard);
else if (ft_strchr(ESCAPMENT, str[i]))
return (InEscapment);
else if (ft_strchr(SEPARATOR, str[i]))
return (InSeparator);
else if (ft_strchr(REDIRECTOR, str[i]))
return (InRedirector);
else if (ft_strchr(FRAME, str[i]))
return (InFrame);
else if (ft_isprint(str[i]) != 0)
return (InIdentifier);
return (InLexError);
}

t_vstate rules_escapment(char *str, int i)
{
static int len = 1;

if (len == 1)
{
len++;
return (InEscapment);
}
else if (ft_strchr(STANDARD, str[i]))
return (len = 1, Standard);
else if (ft_strchr(ESCAPMENT, str[i]))
return (len = 1, InEscapment);
else if (ft_strchr(SEPARATOR, str[i]))
return (len = 1, InSeparator);
else if (ft_strchr(REDIRECTOR, str[i]))
return (len = 1, InRedirector);
else if (ft_strchr(FRAME, str[i]))
return (len = 1, InFrame);
else if (ft_isprint(str[i]) != 0)
return (len = 1, InIdentifier);
return (InLexError);

}

t_vstate rules_identifier(char *str, int i)
{
if (ft_strchr(STANDARD, str[i]))
return (Standard);
else if (ft_strchr(ESCAPMENT, str[i]))
return (InEscapment);
else if (ft_strchr(SEPARATOR, str[i]))
return (InSeparator);
else if (ft_strchr(REDIRECTOR, str[i]))
return (InRedirector);
else if (ft_strchr(FRAME, str[i]))
return (InFrame);
else if (ft_isprint(str[i]) != 0)
return (InIdentifier);
return (InLexError);
}

t_vstate rules_frame(char *str, int i)
{
static int len = 1;
static char c;

if (len == 1)
{
c = str[i - 1];
return (++len, (str[i] == '\0') ? InLexError : InFrame);
}
else if (str[i] == '\0' && c != '\0')
return (InLexError);
else if ((str[i] != c && c != '\0') || (str[i] == c && str[i - 1] == '\\'))
return (InFrame);
else if (str[i] == c)
return (c = '\0', InFrame);
else if (ft_strchr(STANDARD, str[i]))
return (len = 1, Standard);
else if (ft_strchr(ESCAPMENT, str[i]))
return (len = 1, InEscapment);
else if (ft_strchr(SEPARATOR, str[i]))
return (len = 1, InSeparator);
else if (ft_strchr(REDIRECTOR, str[i]))
return (len = 1, InRedirector);
else if (ft_isprint(str[i]) != 0)
return (len = 1, InIdentifier);
return (InLexError);
}

t_vstate rules_separator(char *str, int i)
{
static int len = 1;

if (ft_strchr(SEPARATOR, str[i]) && str[i] != '\0')
{
len++;
if ((len == 2 && (str[i] != str[i - 1] || str[i] == ';')) || len > 2)
return (InLexError);
return (InSeparator);
}
else if (ft_strchr(STANDARD, str[i]))
return (len = 1, Standard);
else if (ft_strchr(ESCAPMENT, str[i]))
return (len = 1, InEscapment);
else if (ft_strchr(REDIRECTOR, str[i]))
return (InSynError);
else if (ft_strchr(FRAME, str[i]))
return (len = 1, InFrame);
else if (ft_isprint(str[i]) != 0)
return (len = 1, InIdentifier);
return (InLexError);
}

t_vstate rules_redirector(char *str, int i)
{
static int len = 1;

if (ft_strchr(REDIRECTOR, str[i]) && str[i] != '\0')
{
len++;
if ((len == 2 && (str[i] != str[i - 1] || str[i] == '|')) || len > 2)
return (InLexError);
return (InRedirector);
}
else if (ft_strchr(STANDARD, str[i]))
return (len = 1, Standard);
else if (ft_strchr(ESCAPMENT, str[i]))
return (len = 1, InEscapment);
else if (ft_strchr(SEPARATOR, str[i]))
return (InSynError);
else if (ft_strchr(FRAME, str[i]))
return (len = 1, InFrame);
else if (ft_isprint(str[i]) != 0)
return (len = 1, InIdentifier);
return (InLexError);
}

RULES.C

Toutes les fonctions qui sont préfixé par ft_ sont pour la plupart des reproduction de celles de la libc du même nom, elle sont dans un libft.a .
Le typedef "t_list" est aussi dans le libft.h, je mettrai ces sources la prochaine fois pour ceux qui voudraient compiler :)

Et le premier petit main (...je ne commenterai pas la fonction d'impression hehe... visez pas la tête!!! :p )

MAIN.C

#include "lexer.h"

static void print_lexed(t_lexer *lexed)
{
t_list *drive;

if (lexed == NULL)
ft_putstr("Malloc error or empty str\n\n");
else
{
ft_putstr("lexed error\t\t");
if (lexed->err == 0)
ft_putendl("None");
else if (lexed->err == InLexError)
ft_putendl("Lexical Error");
else if (lexed->err == InSynError)
ft_putendl("Syntax Error");
ft_putstr("lexed error_type\t");
if (lexed->err_type == None)
ft_putendl("None");
else if (lexed->err_type == Escapment)
ft_putendl("Escapment");
else if (lexed->err_type == Frame)
ft_putendl("Frame");
else if (lexed->err_type == Identifier)
ft_putendl("Identifier");
else if (lexed->err_type == Separator)
ft_putendl("Separator");
else if (lexed->err_type == Redirector)
ft_putendl("Redirector");
ft_putstr("lexed error_index\t");
ft_putnbr(lexed->err_index);
ft_putstr("\n\n");
drive = lexed->tokenlst;
while (drive != NULL)
{
if (((t_token *)(drive->content))->type == None)
ft_putstr("None");
else if (((t_token *)(drive->content))->type == Escapment)
ft_putstr("Escapment");
else if (((t_token *)(drive->content))->type == Frame)
ft_putstr("Frame\t");
else if (((t_token *)(drive->content))->type == Identifier)
ft_putstr("Identifier");
else if (((t_token *)(drive->content))->type == Separator)
ft_putstr("Separator");
else if (((t_token *)(drive->content))->type == Redirector)
ft_putstr("Redirector");
ft_putstr("\t\t[");
ft_putstr(((t_token *)(drive->content))->value);
ft_putendl("]");
drive = drive->next;
}
ft_putchar('\n');
}
ft_putendl("---------------------------------------------------------\n");
}

int main(int ac, char **av)
{
t_lexer *lexed;
int i;

if (ac < 2)
write(1, "To few argument\n", 16);
else
{
i = 1;
while (i < ac)
{
lexed = str_lex(av[i++]);
print_lexed(lexed);
if (lexed)
destroy_lexed(&lexed);
}
}
return (0);
}

MAIN.C

Voilà voilà! Un grand merci à ceux qui se pencheront un peu dessus!

Une chose me tiens à coeur, c'est de ne pas dépasser 25 lignes par fonction..

Bonne continuation à tous!!
0
Rejoignez-nous