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 .