Utilisation de sed pour supprimer des corps de fonctions sur un fichier C / C ++

J’essaie de créer un fichier avec tous les noms de fonction / enum / struct / etc à partir d’un fichier source. Pour cela, je suis en train d’essayer d’utiliser sed pour accomplir quelque chose comme ceci:

(fichier original)

 function add1 (int i) { return i+1; } 

(sortie de sed)

 function add1 (int i) { } 

En d’autres termes, je souhaite supprimer le contenu réel du corps de la fonction. Jusqu’à présent, je ne pouvais pas le faire fonctionner. Aucune suggestion?

EDIT : J’ai essayé quelque chose comme ça, sans succès (pour le moment, j’essaie de ne laisser que les lignes vides du corps de la fonction):

 sed '/{/,/}/ s/.*//' 

Au lieu de sed , vous pouvez toujours utiliser awk en mode de champ par caractère ( FS="" ):

 awk 'BEGIN { RS = "\n" ; FS = "" ; d = 0 ; } { for (i=1; i<=NF; i++) if ($i == "{") { d++ ; if (d == 1) printf "{\n" } else if ($i == "}") { d-- ; if (d == 0) printf "}" } else if (d == 0) printf "%s", $i ; if (d == 0) printf "\n" }' INPUT-FILE(s)... 

Ce qui précède ignorera le contenu des accolades associées, à savoir les corps de fonction et de structure, les initialisations de tableau, etc., et affichera le résultat dans une sortie standard. Vous pouvez spécifier un ou plusieurs fichiers. (Si vous ne spécifiez aucun fichier, il faudra une entrée standard.)

Dans l'état actuel des choses, les accolades entre guillemets ou commentaires restront confuses. Cela pourrait être corrigé de la même manière, mais cela devient vite assez compliqué. Ceci est juste un bidouillage pour vous obtenir la plupart du chemin.

J'ai ajouté les points-virgules ( ; ) afin que vous puissiez simplement tout mettre dans l'extrait de code ci-dessus sur une longue ligne de commande.

La logique du script est très simple. Il utilise le séparateur de champ vide ( FS ), de sorte que chaque caractère en entrée sera son propre champ. La règle BEGIN est exécutée une fois avant le traitement de toute entrée et la configure. Pour les informations relatives au développeur, j’initialise également d = 0 bien que cela ne soit pas nécessaire pour awk car il suppose que les variables non initialisées sont vides ou égales à zéro, selon le cas. Il suivra la profondeur d'accolade actuelle pour chaque caractère saisi.

La seconde expression sera exécutée une fois par enregistrement. Puisque j'ai défini RS = "\n" , chaque ligne est une expression distincte. Ainsi, il sera exécuté une fois par ligne d’entrée. En raison de FS = "" , chaque caractère sur cette ligne constituera un champ séparé. La notice NF champs NF : $1 , $2 , .., $(NF-1) et $NF . La clause if en trois parties affiche simplement les accolades les plus à l'extérieur et tout ce qui n'est pas entre accolades (c'est-à-dire lorsque d == 0 ).

Il est possible d’étendre ce scriptlet awk pour englober les commentaires, les chaînes, les constantes de caractère (utilisez \047 pour faire référence à un guillemet simple, à moins que vous ne placiez le script dans un fichier séparé avec #!/usr/bin/awk -f ), et pour traiter ou ignorer les macros du préprocesseur.

Cela devient un peu compliqué et vous obtiendrez quelques centaines de lignes de script awk, mais il devrait être assez fiable et raisonnablement rapide. Cela est possible parce que les règles de tokenization en C sont faciles à suivre dans ce cas particulier; Personnellement, j'utiliserais un lexer C complet (parsingur lexical ou scanner) dans tous les autres cas d'utilisation. Et probablement pour cela aussi.

Si vous souhaitez utiliser un lexer C complet, vous en trouverez un certain nombre sur le réseau, mais vous devrez utiliser un langage de niveau supérieur, tel que C ou C ++. Si vous souhaitez gérer tous les cas critiques, il faudra également incorporer un préprocesseur C / C ++, mais ces règles sont simples (même avec awk).

Sur un fichier au format cohérent, vous pouvez faire quelque chose comme:

 sed '/{$/ {:r;/\n}/!{N;br}; s/\n.*\n/\n/}' 

lire immédiatement le corps de la fonction et tout supprimer entre accolades:

 $ echo 'function add1 (int i) { if (i == 1) {return i+1;} }' | sed '/{$/ {:r;/\n}/!{N;br}; s/\n.*\n/\n/}' function add1 (int i) { } 

La commande ne fonctionne que sur les blocs commençant par { juste avant et se terminant par a } juste après une nouvelle ligne.

Dans la partie :r;/\n}/!{N;br} :r définit une étiquette nommée r dans laquelle une autre ligne est ajoutée à l’espace modèle à partir de l’entrée ( N ), puis le stream d’exécution revient au début. de r nouveau ( br ). Cela n’arrive que jusqu’à ce que \n} soit rencontré. Ainsi, lorsque nous sums hors de cette “boucle”, nous avons le corps entier de la fonction dans l’espace de modèle, puis nous appliquons la commande s .

Je voudrais d’abord suggérer de s’assurer que votre fichier source C est correctement mis en retrait. Vous pouvez utiliser indent -gnu pour cela.

Ensuite, vous pourriez utiliser quelques astuces sed . Avec un code correctement mis en retrait, vous devez uniquement vous soucier des accolades (ouverture ou fermeture) en tant que premier caractère de leurs lignes.

Je ne suis pas sûr de deviner pourquoi vous voulez faire cela. En particulier, struct peut être et est parfois vraiment nested. Et il existe des cas pathologiques, par exemple des macros de préprocesseur définissant des éléments avec des accolades, etc.

Un meilleur moyen peut être d’opérer sur les internes du compilateur (mais vous devez alors gérer des éléments provenant des en-têtes #include -d). Vous pouvez utiliser MELT à cette fin (MELT est un langage de haut niveau spécifique au domaine pour étendre GCC et travaille sur les composants internes de GCC).