Fonction Buildargv avec Ragel

Utilisation amusante du compilateur Ragel State Machine pour créer une fonction d'analyse de ligne sur int argc, char * argv [].


Tout a commencé avec le fait que la fonction buildargv était nécessaire pour analyser la chaîne pour un transfert ultérieur vers


int main (int argc, char *argv[]) { body } 

Eh bien, je pensais, il ne pouvait pas être impossible d'emprunter n'importe où, maintenant nous trouvons ... Et je n'ai pas trouvé ...



Eh bien, non pas que je ne l'aurais pas du tout trouvé, par exemple, https://github.com/gcc-mirror/gcc/blob/master/libiberty/argv.c (la GPLv2 est toujours bonne), j'accepte immédiatement de telles obligations n'était pas prêt. Il y a certainement une telle fonction dans bash (la GPLv3 est encore meilleure). zsh? - allez trouver (j'ai trouvé ... - je ne veux pas).


En général, je n'ai pas trouvé ce que je voulais, mais je n'ai pas aimé ce que j'ai trouvé. Bon, au final, j'y ai droit, tout de même, je me fais une soif de divertissement en cours de route.


Je ne voulais pas du tout écrire cette affaire de manière conventionnelle à partir du mot, j'étais même bouleversé pour ce motif.


En général, nous rencontrons le compilateur Ragel State Machine.


Boîte à outils


  • gcc;)
  • ragel
  • faire
  • lcov
  • libcheck

Le projet se trouve ici: JOYFUL CMDLINE PARSER ÉCRIT EN RAGEL


Énoncé du problème


En entrée, nous avons une chaîne de tout type, la tâche consiste à obtenir de la chaîne un tableau d'arguments séparés par un espace ou une tabulation, avec:


  • Tout caractère suivant le caractère d'échappement \ doit être ignoré.
  • Tous les personnages qui sont entre deux doubles ou doivent
    être considéré comme un élément
  • En cas de ' ou " non fermé, une erreur doit être retournée

En général, il n'y a pas beaucoup de conditions. Et Ragel convient parfaitement à cette tâche.


Mise en œuvre expliquée


Déclarons une machine avec le nom "buildargv" et demandons à Ragel de placer ses données au début du fichier (5.8.1 Écrire des données).


 %%{ machine buildargv; write data; }%% 

Ensuite, nous déclarons une machine lineElement , qui à son tour consiste à combiner (2.5.1 Union) deux machines: arg et whitespace .


 lineElement = arg >start_arg %end_arg | whitespace; main := blineElements**; 

En entrée et en sortie de la machine arg , les actions start_arg et end_arg respectivement.


 action start_arg { argv_s = p; } action end_arg { nargv = (char**)realloc((*argv), (argc_ + 1)*sizeof(char*)); (*argv) = nargv; (*argv)[argc_] = strndup(argv_s, p - argv_s); argc_++; } 

De plus, la tâche start_arg pour enregistrer la position du caractère à l'entrée et la tâche end_arg ajoutent un nouvel élément au tableau argv , en cas de sortie réussie de la machine arg .


Examinons maintenant de plus près l' arg .


 arg = '\''> { fcall squote; } | '"'>{ fcall dquote; } | ( '\\'>{fcall skip;} | ^[ \t"'\\] )+; 

Il consiste en une union de trois machines ' , " et (\ | ^[ \t"'\]) , cette dernière étant à son tour une union de \ et ^[ \t"'\] respectivement.


Lorsque nous trouvons le caractère ' nous appelons squote ' nous appelons squote , ou si le caractère actuel est \ call skip , ce qui saute tout caractère qui le suit, et aucun caractère n'est différent de 0x20 (espace), 0x09 (tab), ' , " ou \ est considéré comme correct .


Il reste à considérer une toute petite partie:


 skip := any @{ fret; }; dquote := ( '\\'>{ fcall skip; } | ^[\\] )+ :> ["] @{ fret; } @err(dquote_err); squote := ( '\\'>{ fcall skip; } | ^[\\] )+ :> ['] @{ fret; } @err(squote_err); 

Avec skip nous avons déjà compris ce que ^['\\] ne devrait pas non plus poser de questions. Et ici :> il s'agit de Entry-Guarded Concatenation (4.2 opérateurs surveillés qui encapsulent les priorités). Sa signification est que la machine ( '\\'>{ fcall skip; } | ^['\\] )+ termine son exécution lorsque ["] revient à l'état initial.


Et enfin, dans le cas d'une erreur de fin de ligne avec des guillemets ouverts, dquote_err et squote_err pour indiquer et définir le code d'erreur correspondant.


 action dquote_err { ret = -1; errsv = BUILDARGV_EDQUOTE; } action squote_err { ret = -1; errsv = BUILDARGV_ESQUOTE; } 

La génération du code est effectuée par la commande:


 ragel -e -L -F0 -o buildargv.c buildargv.rl 

Une liste de lignes de test peut être trouvée dans test_cmdline.c .


Conclusion


Le problème est résolu.


C'était plus rapide? J'en doute. Plus clair? Si seulement vous êtes un expert de Ragel.


Je ne prétends pas à l'absolutisme, je serai reconnaissant pour les commentaires constructifs sur le code Ragel.


Liste des matériaux:


[^ 1]: Adrian Thurston. Compilateur de machines d'état Ragel .

Source: https://habr.com/ru/post/fr477296/


All Articles