Crie um pré-processador C

18

O objetivo é criar um pré-processador para a linguagem C, o menor possível em termos de tamanho do código-fonte em bytes , no idioma preferido. Sua entrada será um arquivo de origem C e sua saída será o código-fonte pré-processado.

Os itens que ele deverá poder processar são: remoção de comentários (linha / bloco), #include diretivas (abrindo arquivos em caminhos relativos e substituindo o texto no ponto necessário), #define, #undef, #if, #elif, #else, #endif, #ifdef, #ifndef e defined (). Outras diretivas do pré-processador C, como #pragmas ou #errors, podem ser ignoradas.

Não há necessidade de calcular expressões aritméticas ou operadores de comparação nas diretivas #if, assumimos que a expressão será avaliada como verdadeira desde que contenha um número inteiro diferente de zero (seu principal uso será a diretiva definida ()). Exemplos de possíveis entradas e saídas a seguir (possíveis espaços em branco extras nos arquivos de saída foram aparados para melhor aparência, não é necessário que seu código faça isso). Um programa capaz de processar adequadamente os seguintes exemplos será considerado suficiente.

----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
fonte
Você pode fornecer amostras de entrada / saída?
Florent
Forneça-nos um código de teste. É quase impossível sem exemplos.
Ismael Miguel
Claro que sim. Apenas seja um pouco paciente, pois não posso ser muito rápido devido a restrições de tempo e carga de trabalho.
Thanasis Papoutsidakis
1
Quanto #ifprecisa ser suportado? ou seja, o pré-processador precisa suportar expressões com operações aritméticas, bit a bit, etc.?
Hasturkun
ok, entrada / saída de exemplo e mais explicações adicionadas #
Thanasis Papoutsidakis

Respostas:

8

Flex, 1170 + 4 = 1174

1170 caracteres no código flexível + 4 caracteres para um sinalizador de compilação. Para produzir um executável, execute flex pre.l ; gcc lex.yy.c -lfl. A entrada vaza memória como uma peneira e não fecha os arquivos incluídos. Mas, caso contrário, deve ser completamente funcional conforme as especificações.

%{
#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)}

Alguma explicação:

  • ae bsão temporários para conter seqüências de caracteres da entrada. atambém é usado como parâmetro para funcionar f.
  • vmantém os nomes das macros e Vmantém os valores 'V' das macros
  • te Tsão titulares temporários para quando crescermos veV
  • i é um incrementador de loops
  • s é o tamanho da matriz de macro
  • oé a contagem dos 'o'pen ifs dentro de um falso condicional
  • g() mostra as matrizes de macro
  • f()'f'inds uma macro com o mesmo valor em vcomoa
  • d(y)'apaga os últimos ycaracteres da entrada atual
  • estado Dé para dentro de um 'D'efine
  • O estado Fé para ignorar uma condição 'F'alse
  • O estado Ié para 'Estou ignorando else/ elifdepois que um condicional verdadeiro foi encontrado.

EDIT1: limpou muitos vazamentos de memória e implementou o fechamento de arquivos

EDIT2: código modificado para manipular macros aninhadas mais corretamente

EDIT3: quantidade louca de golfe

EDIT4: mais golfe

EDIT5: mais golfe; Também notei que minha chamada para fclose () causa problemas em alguns computadores ... investigando isso.

Josh
fonte
Até agora, funciona muito bem na maioria dos casos ... por algum motivo, gera uma falha de segmentação quando eu entro #include, mas acho que isso está relacionado ao bug na edição # 5. Além disso, ele não substitui as macros, mesmo que processe com êxito os blocos #if - a menos que eu esteja fazendo algo errado ... mas, em geral, parece muito bom e fornece uma idéia aproximada do que um lexer pode fazer, pois Eu posso entender isso mesmo em sua forma de golfe. Tente ver se os bugs podem ser corrigidos, se não estiver ok, como o código se explica bem, provavelmente essa será a resposta escolhida, pois não há outras entradas.
Thanasis Papoutsidakis