Créer un préprocesseur C

18

L'objectif est de créer un préprocesseur pour le langage C, aussi petit que possible en termes de taille de code source en octets , dans votre langue préférée. Son entrée sera un fichier source C et sa sortie sera le code source prétraité.

Les éléments qu'il devra être en mesure de traiter seront: la suppression des commentaires (ligne / bloc), les directives #include (en ouvrant les fichiers aux chemins relatifs et en remplaçant le texte au point requis), #define, #undef, #if, #elif, #else, #endif, #ifdef, #ifndef et defined (). D'autres directives de préprocesseur C comme #pragmas ou #errors peuvent être ignorées.

Il n'est pas nécessaire de calculer des expressions arithmétiques ou des opérateurs de comparaison dans les directives #if, nous supposons que l'expression sera évaluée à true tant qu'elle contient un entier différent de zéro (son utilisation principale sera pour la directive defined ()). Des exemples d'entrées et de sorties possibles suivent (les éventuels espaces blancs supplémentaires dans les fichiers de sortie ont été coupés pour une meilleure apparence, il n'est pas nécessaire que votre code le fasse). Un programme capable de traiter correctement les exemples suivants sera considéré comme suffisant.

----Input file: foo.c (main file being preprocessed)

#include "bar.h" // Line may or may not exist

#ifdef NEEDS_BAZZER
#include "baz.h"
#endif // NEEDS_BAZZER

#ifdef _BAZ_H_

int main(int argc, char ** argv)
{
    /*  Main function.
        In case that bar.h defined NEEDS_BAZ as true,
        we call baz.h's macro BAZZER with the length of the
        program's argument list. */
    return BAZZER(argc);
}

#elif defined(_BAR_H_)

// In case that bar.h was included but didn't define NEEDS_BAZ.
#undef _BAR_H_
#define NEEDS_BARRER
#include "bar.h"

int main(int argc, char ** argv)
{
    return BARRER(argc);
}

#else

// In case that bar.h wasn't included at all.
int main()
{return 0;}

#endif // _BAZ_H_

----Input file bar.h (Included header)

#ifndef _BAR_H_
#define _BAR_H_

#ifdef NEEDS_BARRER

int bar(int * i)
{
    *i += 4 + *i;
    return *i;
}

#define BARRER(i) (bar(&i), i*=2, bar(&i))

#else
#define NEEDS_BAZZER // Line may or may not exist
#endif // NEEDS_BARRER

#endif // _BAR_H_

----Input file baz.h (Included header)

#ifndef _BAZ_H_
#define _BAZ_H_

int baz(int * i)
{
    *i = 4 * (*i + 2);
    return *i;
}

#define BAZZER(i) (baz(&i), i+=2, baz(&i))

#endif // _BAZ_H_

----Output file foopp.c (no edits)

int baz(int * i)
{
    *i = 4 * (*i + 2);
    return *i;
}

int main(int argc, char ** argv)
{
    return (baz(&argc), argc+=2, baz(&argc));
}

----Output file foopp2.c (with foo.c's first line removed)

int main()
{return 0;}

----Output file foopp3.c (with bar.h's line "#define NEEDS_BAZZER" removed)

int bar(int * i)
{
    *i += 4 + *i;
    return *i;
}

int main(int argc, char ** argv)
{
    return (bar(&argc), argc*=2, bar(&argc));
}
Thanasis Papoutsidakis
la source
Pouvez-vous fournir des échantillons d'entrée / sortie?
Florent
Fournissez-nous un code de test. C'est presque impossible sans exemples.
Ismael Miguel
Bien sûr que je le ferai. Soyez juste un peu patient car je ne peux pas être très rapide à cause des contraintes de temps et de charge de travail.
Thanasis Papoutsidakis
1
Combien de #ifbesoins doivent être pris en charge? c'est-à-dire que le préprocesseur doit prendre en charge les expressions avec des opérations arithmétiques, au niveau du bit, etc.?
Hasturkun
ok, exemple d'entrée / sortie et autres explications ajoutées
Thanasis Papoutsidakis

Réponses:

8

Flex, 1170 + 4 = 1174

1170 caractères dans le code flexible + 4 caractères pour un indicateur de compilation. Pour produire un exécutable, exécutez flex pre.l ; gcc lex.yy.c -lfl. L'entrée fuit la mémoire comme un tamis et ne ferme pas les fichiers inclus. Mais sinon, il devrait être complètement fonctionnel selon les spécifications.

%{
#define M malloc
#define X yytext
#define A a=X
#define B(x) BEGIN x;
#define Y YY_CURRENT_BUFFER
*a,*b,**v,**V,**t,**T,i,s=1,o;
g(){t=M(++s);T=M(s);for(i=1;i<s-1;i++)t[i]=v[i],T[i]=V[i];free(v);free(V);v=t;V=T;}
f(){for(i=1;i<s;i++)if(!strcmp(v[i],a))return i;return 0;}
d(y){X[yyleng-y]=0;}
%}
%x D F I
N .*\n
%%
"//".*
"/*"([^\*]|\*[^\/])*"*/"
\"(\\.|[^\\"])*\" ECHO;
^"#include "\"[^\"]*\" d(1),yypush_buffer_state(yy_create_buffer(fopen(X+10,"r"),YY_BUF_SIZE));
^"#define "[^ ]* {B(D)strcpy(a=M(yyleng),X+8);}
<D>" "?{N} {b=M(yyleng);d(1);f(strcpy(b,X+(X[0]==32)))?free(V[i]),V[i]=b:g(),v[s-1]=a,V[s-1]=b;B(0)}
^"#undef "{N} d(1),v[f(A+7)][0]=0;
^"#if defined(".*")\n" h(2,12);
^"#ifdef "{N} h(1,7);
^"#if "{N} {d(1);if(!atoi(X+4))B(F)}
^"#ifndef "{N} {d(1);if(f(A+8))B(F)}
<F>^"#if"{N} o++;
<F>^"#endif"{N} if(!o--)B(++o)
<F>^"#else"{N} if(!o)B(0)
<F>^"#elif defined(".*")\n" if(!o){d(2);if(f(A+14))B(0)}
<F>^"#elif "{N} if(!o){d(1);if(atoi(X+6))B(0)}
<F>{N}
^"#endif"{N}
^"#el"("se"|"if"){N} B(I)
<I>^"#endif"{N} B(0)
<I>{N}
[a-zA-Z_][a-zA-Z_0-9]* printf(f(A)?V[i]:a);
<<EOF>> {a=Y;yypop_buffer_state();if(!Y)exit(0);fclose(a);}
%%
h(x,y){d(x);if(!f(A+y))B(F)}

Quelques explications:

  • aet bsont des temps pour maintenir les chaînes de l'entrée. aest également utilisé comme paramètre pour fonctionner f.
  • vcontient les noms des macros et Vcontient les valeurs «V» des macros
  • tet Tsont des détenteurs temporaires pour quand nous grandissons vetV
  • i est un 'i'ncrementer pour les boucles
  • s est la taille du tableau de macro
  • oest le nombre de 'o'pen ifs dans un faux conditionnel
  • g() 'g'rows les macro tableaux
  • f()'f' trouve une macro avec la même valeur vque asa
  • d(y)'supprime les derniers ycaractères de l'entrée actuelle
  • l'état Dest pour l'intérieur d'un 'D'efine
  • l'état Fest pour ignorer une conditionnelle «F»
  • l'état Iest pour 'I'gnoring else/ elifaprès qu'un vrai conditionnel a été trouvé.

EDIT1: nettoyé de nombreuses fuites de mémoire et implémenté la fermeture de fichiers

EDIT2: code modifié pour gérer plus correctement les macros imbriquées

EDIT3: une quantité folle de golf

EDIT4: plus de golf

EDIT5: plus de golf; J'ai également remarqué que mon appel à fclose () provoque des problèmes sur certains ordinateurs ...

Josh
la source
Cela fonctionne très bien jusqu'à présent dans la plupart des cas ... pour une raison quelconque, il jette un défaut de segmentation lorsque je #includebourre, mais je suppose que cela est lié au bogue dans l'édition # 5. De plus, il ne remplace pas les macros, même s'il traite avec succès les blocs #if - à moins que je fasse quelque chose de mal ... mais en général, il semble très bon, et il donne une idée approximative de ce qu'un lexer peut faire, car Je peux le comprendre même sous sa forme golfée. Essayez de voir si les bugs peuvent être corrigés, sinon ce n'est pas grave, comme le code s'explique bien, probablement ce sera la réponse choisie car il n'y a pas d'autres entrées.
Thanasis Papoutsidakis