Formater kodu źródłowego napisanego w języku ANSI C

    -- Sebastian Pawlak, 2002.


"Nie istnieją żadne absolutne reguły stylu programowania tak samo jak stylu pisania, ponieważ programowanie jest częściowo sztuką, a częściowo nauką." -- McCracken, Salmon, 1987

Język C, mimo wielu lat, które minęły od jego powstania, cieszy się ogromną popularnością. Składnia języka pozwala na pisanie efektywnych programów przy stosunkowo niewielkim rozmiarze kodu. Niestety udogodnienia wprowadzone w C, przy nieumiejętnym ich stosowaniu oraz nadużywaniu, prowadzą do stworzenia programu zupełnie niezrozumiałego dla człowieka. Dlatego ważne jest aby programować w sposób przejrzysty, co znakomicie ułatwia późniejszą analizę.
Ogromne znaczenie ma także styl programowania, w aspekcie właściwego formatowania tekstu źródłowego programu. Nie istnieje jeden właściwy styl programowania. Pewne jest natomiast, że dobry styl powinien ułatwiać analizę kodu źródłowego oraz podkreślać jego strukturę.

"Wybierz styl, który Ci najbardziej odpowiada, i stosuj go konsekwentnie we wszystkich programach." -- Brain W. Kernighan, Dennis M. Ritchie - "Język ANSI C", 1978)

Jakkolwiek tylko od programisty zależy napisanie poprawnie działającego i spójnego programu, to formatowanie kodu źródłowego można zautomatyzować. Stworzenie pełnowartościowego programu formatującego kod źródłowy napisany w języku ANSI C nie jest zadaniem trywialnym.
Załóżmy, że chcemy aby w sformatowanym kodzie źródłowym operator arytmetyczny "*" (iloczyn) był otoczony z obu stron spacjami, natomiast "*" jako operator używany w deklaracji wskaźnika miał spację po lewej stronie, a "*" jako operator wyłuskania nie miał spacji. Wtedy z takiego oto fragmentu kodu źródłowego:

FILE*f;
int i=2*2,
  *   a=2;

Oczekujemy takiego, sformatowanego:

FILE *f;
int i = 2 * 2,
    *a = 2;

Aby program mógł rozróżnić w jakim znaczeniu użyty został operator "*" należałoby przeprowadzać analizę składni. Żeby uwzględnić różne aspekty składnii języka ANSI C i generować odpowiednio sformatowany kod, trzebaby zbudować złożony parser. Program taki byłby bardzo rozbudowany (sama gramatyka języka ANSI C i preprocesora zajmują 8 stron w książce "Język ANSI C" Briana W. Kernighana i Dennisa M. Ritchie). Gdyby stworzyć tak złożony program to prawdopodobnie i tak nie byłby on optymalny. Do tworzenia parserów przeważnie stosuje się narzędzia typu Bison, które generują szybko działające programy. Rzadko pisze się parser "od zera". Przykładem w pełni funkcjonalnego programu formatującego kod źródłowy języka C jest, dostępny np. pod Linuksem, program Indent. Jest to bardzo skomplikowane narzędzie, którego tworzeniem zajmuje się od dawna grupa programistów. Istnieje także, bardzo wiekowy, program CB (Code Beautifier), który mimo dość sporych rozmiarów, ogranicza się do robienia wcięć, nie ingerując w wygląd poszczególnych wierszy kodu.
Analiza kodu, wyżej wymienionych programów, dowodzi faktu, jak złożonym problemem jest napisanie w pełni funkcjonalnego programu formatującego.

Ze względu na rozległość zagadnienia postanowiłem skupić się na formatowaniu kodu źródłowego w aspekcie dokonywania wcięć dla zagnieżdzonych instrukcji i bloków. Mimo przyjętego ograniczenia, program i tak jest stosunkowo złożony (w języku C występuje kilka różnych instrukcji wymagających oprogramowania ; występują komentarze, napisy).
Zakładam, że program nie ingeruje w format poszczególnych linii kodu. Wykonywane są wcięcia dla instrukcji (for, while, if, switch) oraz bloków ( {, }).

Przykładowo, dla poniższego fragmentu kodu:

int main(void) { int a = 0, i = 0, b; /* zmienne */
while(i < 5) if (i < 3) { printf("{i<3}"); i++; } else { printf"{i>=3}"); i++; }
for (;;) if (i>2) while (i>=0) if (a==3 && b<2) i--; else i++; else a--;}


Po formatowaniu otrzymamy:


int main(void) 
{
    int a = 0, i = 0, b;/* zmienne */

    while (i < 5)
        if  (i < 3)
        {
            printf("{i<3}");
            i++;
        }
        else
        {
            printf"{i>=3}");
            i++;
        }
    for  (;;)
        if  (i>2)
            while  (i>=0)
                if  (a==3 && b<2)
                    i--;
                else
                    i++;
        else
            a--;
}

Nie zajmuję się także formatowaniem instrukcji preprocesora.
Zakładam, że formatowany program jest poprawnym programem napisanym w języku ANSI C.
Każdy nawias klamrowy jest w nowym wierszu - reguła ta nie omija np. deklaracji tablic z jednoczesną ich inicjalizacją (tam używane są nawiasy klamrowe).



Przegląd znanych metod rozwiązania zadania

Istnieją dwa, znane mi, podejścia do zagadnienia będącego przedmiotem tego projektu. Jak już wspomniałem w poprzednim punkcie, proste podejście reprezentuje program CB. Program ten nie ingeruje w format poszczególnych wierszy programu, a jedynie dokonuje wcięć poleceń zawartych w blokach ( {, } ) oraz wcięć komend przypisanych do niektórych poleceń (while, if, else, for, ...). Zasada działania programu oparta jest o implementację prostego automatu, który odczytuje z wejścia pojedyncze znaki i na ich podstawie podejmuje odpowiednie akcje. CB nie dokonuje analizy składniowej. Wykrywa jedynie podstawowe polecenia oraz znaki początku i końca bloku i wstawia przed zagnieżdżonymi wyrażeniami odpowiednią liczbę spacji.

Drugie podejście do problemu to stworzenie parsera, który analizuje kod programu i generuje odpowiednio sformatowany tekst. Taki program jest na pewno bardziej funkcjonalny. Pozwala formatować poszczególne wiersze kodu. W punkcie pierwszym opisałem problem różnorodnego formatowania wyrażeń z operatorem "*", zależnie od kontekstu w jakim występuje.
Bezsprzecznie to drugie podejście pozwala na stworzenie bardzo dobrego narzędzia. Niestety, objętość samej gramatyki języka ANSI C, jak również złożoność np. programu Indent (dokonującego wszechstronnego formatowania) pokazują praktyczną niemożliwość zaimplementowanie programu w tej wersji. Rozwiązanie bazujące na parserze ma też tę zaletę, że możliwe jest analizowanie poprawności programu. Jeśli np. użytkownik zapomniał domknąć jakiegoś nawiasu albo np. po lewej stronie wyrażenia przypisania nie występuje l-wartość to można wypisać komunikat o błędzie.



Opis metody rozwiązania

Wybór sposobu działania programu nie pociąga za sobą żadnych specjalnych wymagań co do sprzętu oraz nie powoduje innych nadzwyczajnych konsekwencji. Program mój został napisany w języku ANSI C. Nie wymaga zbyt dużej mocy obliczeniowej lub obszernych zasobów pamięci.
Poniżej przedstawiam poglądowy schemat działania algorytmu.

1. Weź kolejny znak z pliku wejściowego (gdy koniec pliku to zakończ algorytm).

2. W zależności od znaku wykonaj:
   a) Spacja lub Tabulacja:
       - Jeśli wcześniej w tym wierszu wystąpił czarny znak,
         to wyprowadź biały znak na wyjście.
   b) Znak początku nowego wiersza:
       - Jeśli w tym wierszu nie wystąpił jeszcze czarny znak,
         to pomiń ten punkt.
       - W przeciwnym wypadku wyprowadź znak początku nowego
         wiersza na wyjście.
   c) Średnik:
       - Wyprowadź znak średnika na wyjście.
       - Zmniejsz odpowiednio zagłębienie we wcięciu.
   d) Klamrowy nawias otwierający:
       - Jeśli wcześniej w tym wierszu wystąpił czarny znak,
         to wyprowadź na wyjście znak początku nowego wiersza.
       - Zrób wcięcie.
       - Na wyjście wyprowadź znak { a po nim znak początku
         nowego wiersza,
   e) Klamrowy nawias zamykający:
       - Jeśli wcześniej w tym wierszu wystąpił czarny znak,
         to wyprowadź na wyjście znak początku nowego wiersza.
       - Zrób wcięcie (odpowiednio mniej zagłębione).
       - Na wyjście wyprowadź znak } a po nim znak początku
         nowego wiersza,
   f) Cudzysłów albo Apostrof:
       - Wypisz na wyjście literał napisowy albo znakowy
         (wypisanie w tym miejscu "napisu" albo 'znaku'
         zapobiega analizie napisu/znaku ; np. napis "/* ;"
         nie będzie traktowany jak początek komentarza).
   g) Ukośnik /
       - Pobierz kolejny znak.
       - Jeśli pobrany znak nie jest gwiazdką *, to zwróć go
         z powrotem na wejście i omiń ten punkt.
       - Wypisz komentarz na wyjście (wypisanie w tym miejscu
         komentarza zapobiega jego analizie a tym samym
         formatowaniu jego zawartości).
   h) Znak nie występujący powyżej (np. litery, cyfry,
      operatory arytmetyncze, ...)
       - Wypisz znak na wyjście.
       - Zapisz znak na kolejnej pozycji bufora pomocniczego.
       - Jeśli w buforze pomoniczym uzyskano napis "else",
         to zrób odpowiednie wcięcie dla poleceń pod "else".
       - Jeśli w buforze pomocniczym uzyskano napis "for",
         "if", "while" albo "switch" to zrób odpowiednie
         wcięcie dla poleceń pod spodem (w tym miejscu odpowiednio
         wypisywane są parametry powyższych instrukcji zawarte
         w nawiasach).

3. Skocz do punktu 1.

W programie występuje jeszcze kilka algorytmów pomocniczych. Np. czasem potrzeba podejrzeć kilka znaków na przód a potem zwrócić je z powrotem na wejście. W standardowej bibliotece ANSI C występuje funkcja ungetc zwracająca pobrany znak na wejście - niestety pozwala ona na zwrot jedynie jednego znaku. W celu obejścia tego problemu napisałem własne funkcje operacji na znakach.

W moim algorytmie pobieram z wejścia znak po znaku i w zależności od nich podejmuję odpowiednie działania.



Analiza poprawności działania

- We wczesnych latach sześćdziesiątych z powodu błędu w komputerowym programie sterującym lotem utracono jeden z amerykańskich statków kosmicznych z serii Mariner, wysłany w kierunku Wenus; przyniosło to straty w wysokości milionów dolarów.

- W roku 1981, w jednej ze stacji telewizyjnych prowadzącej sprawozdania z wyborów regionalnych w prowincji Quebec w Kanadzie, na podstawie wyników błędnych programów komputerowych wysnuto wniosek, że osiągnęła prowadzenie pewna niewielka partia, o której początkowo nikt nie sądził, aby mogła mieć jakiekolwiek szanse. Tę informację, wraz z wywołanymi nią wypowiedziami komentatorów, przekazano milionom telewidzów.

To zaledwie dwa z licznych przykładów błędów w oprogramowaniu, z których wiele zakończyło się katastrofami, często związanymi z utratą ludzkiego życia. Wagi zagadnienia poprawności przecenić nie sposób. -- David Harel - "Rzecz o istocie informatyki. Algorytmika", 1978

Czy można w pełni ufać wynikom działania programu?
Absolutnego przekonania co do poprawności algorytmu nie można uzyskać bez przeprowadzenia formalnego dowodu.

Dowód poprawności podany w poprzednim podrozdziale (dowód poprawności trywialnego algorytmu odwracającego napis) jest jednym z najprostszych w swoim rodzaju, a przecież nie całkowicie banalny. -- David Harel - "Rzecz o istocie informatyki. Algorytmika", 1978

Niestety dowody poprawności, ze względu na ich nietrywialność, stosuje się dla prostych algorytmów. W praktyce nie dowodzi się poprawności większych programów.

Na szczęście nie zostajemy skazani na wiarę w nieomylność programisty. Istnieją metody badania poprawności oprogramowania. Powszechnie stosowane jest testowanie zestawami odpowiednio przygotowanych danych. Sposób ten nie gwarantuje, że wszystkie błędy zostaną wyeliminowane, ale daje możliwość zawężenia ich liczby.

W przypadku tego projektu można wyróżnić dwa rodzaje zagrożeń wynikające z błędnego działania programu formatującego. Pierwsze, mniej groźne, objawia się tym, że na skutek jakiegoś błedu wynikowy kod jest nieprawidłowo sformatowany. Drugie, groźniejsze powodowałoby uszkodzenie kodu wynikowego tak, że nie dałby się on skompilować albo program po kompilacji działałby błędnie.

Korzystając z narzędzi dostępnych w systemie operacyjnym Linux można wykonać odpowiednie testy sprawdzające czy program formatujący nie powoduje uszkodzeń kodu formatowanego programu.
Oto sposób postępowania:

1. Skompilowanie oryginalnego programu do "obiektu". 
     gcc -c program.c
     Operacja ta spowoduje powstanie pliku program.o.
2. Sformatowanie kodu źródłowego moim programem.
    ./formater program.c programPrzetworzony.c
     Operacja ta spowoduje powstanie pliku programPrzetworzony.c
3. Skompilowanie przetworzonego programu formatowanego do "obiektu". 
     gcc -c programPrzetworzony.c
     Operacja ta spowoduje powstanie pliku programPrzetworzony.o.
4. Teraz sprawdzam czy oba obiekty są identyczne.
    diff program.o programPrzetworzony.o
    Jeśli oba pliki są identyczne to znaczy, że nie nastąpiło uszkodzenie kodu.

Przedstawiona metoda testowania daje 100% gwarancję, że dany program nie został uszkodzony, podczas formatowania jego kodu źródłowego.

Druga kategoria przeprowadzonych testów polegała na obserwacji czy program prawidłowo formatuje kod.
Tu postanowiłem przygotować zestawy danych testowych. Dane te to odpowiednio przygotowane fragmenty kodu. Po przetwarzaniu tych danych moim programem weryfikowałem poprawność uzyskanych efektów.
Przygotowany przeze mnie kod testowy zawierał różne kombinacje instrukcji wymuszających wcięcia. Po takich instrukacjach występowały zarówno instrukcje pojedyncze, bloki { } jak i kolejne instrukcje wymagające formatowania.
Poprawność rezultatów testowałem również na gotowych program w ANSI C.

Przeprowadzone przeze mnie testy wydają się być najbardziej odpowiednie w tym projekcie. Dwie zaproponowane metody testowania pozwalają wychwycić błędy z opisanych wcześniej kategorii.

Wyniki testów przeprowadzonych pod kątem wychwycenia ewentualnych uszkodzeń kodu nie wykazały żadnej destrukcyjnej działalności mojego programu.
Testy weryfikujące poprawność formatowania nie wykazały niezgodności z moimi założeniami.



Uwagi końcowe

Istnieje możliwość dokonania poprawek w programie. Głównie mogą one polegać na zmianie stylu generowanego, sformatowanego kodu źródłowego. Można także oprogramować, nieuwzględnione w założeniach projektu, aspekty języka C - np. dodać formatowanie kodu preprocesora.
Moje rozwiązanie nie jest optymalne. Jak niejednokrotnie podkreślałem, program nie bazujący na analizie składnii nie jest w pełni funkcjonalny. Co do możliwości wprowadzania ulepszeń to uważam, że będą miały one charakter jedynie kosmetyczny. Gdyby chcieć wprowadzić zasadnicze ulepszenia to trzebaby zastosować podejście oparte na parsowaniu kodu źródłowego. To niestety wymaga tworzenia programu od nowa a nie ulepszanie istniejącego. Uważam, że program w tej wersji można ulepszać, ale w ograniczonym zakresie.


Kod źródłowy pliku "formater.c":

/*******************************************************
 * Formater kodu zrodlowego napisanego w jezyku ANSI C *
 *******************************************************/

#include <stdio.h>
#include <string.h>  /* dla strcmp() */
#include <ctype.h>   /* dla isalnum(), ... */

#define SZEROKOSC_WCIECIA 4  /* szerokosc jednego wciecia w znakach */

FILE *f1, *f2;            /* uchwyt do pliku wejsciowego i wyjsciowego */
int pierwszyZnakWiersza;  /* flaga okreslajaca czy w danym wierszy byl juz czarny znak */
int wciecie;              /* aktualny poziom zaglebienia wciecia */

#define MAX_BUF 16
char buf[MAX_BUF + 1] = { 0 };  /* pomocniczy bufor na znaki */
int spBuf = 0;                  /* wskaznik wolnego znaku w buforze pomocniczym */

/* deklaracje funkcji */
int wezZnak(void);
int oddajZnak(int c);

int main(int argc, char *argv[])
{
    /* deklaracje funkcji */
    void zrobWciecie(int wciecie);
    int ominKomentarz(void);
    void wypiszBufor(void);

    int c, cl, cp, k;
    int srednik = 0;
    int wciecieStare = 0;
    int wcieciaTab[32] = { 0 };
    int coSpowodowaloWciecie[32] = { 0 }; /* wartosc 1-wciecie spowodowal if ; 0-cos innego */

    printf("Formater kodu zrodlowego napisanego w jezyku ANSI C.\n");
    
    /* jesli w linii polecen nie podano dwoch wymaganych argumentow to wypisuje komunikat */
    if (argc != 3) {
        fprintf(stderr, "Brak parametrow!\nmain plik_wejsciowy plik_wyjsciowy\n");
        return -1;
    }

    /* otwarcie pliku z programem do formatowania */
    if ((f1 = fopen(argv[1], "r")) == NULL) {
        fprintf(stderr, "Nie moge otworzyc pliku wejsciowego %s!\n", argv[1]);
        return -1;
    } else
        printf("Plik wejsciowy otwarty poprawnie.\n");

    /* utworzenie pliku do zapisu wyniku formatowania */
    if ((f2 = fopen(argv[2], "w+")) == NULL) {
        fprintf(stderr, "Nie moge utworzyc pliku wynikowego %s!\n", argv[2]);
        return -1;
    } else
        printf("Plik wyjsciowy utworzony poprawnie.\n");

    printf("Rozpoczynam formatowanie zadanego kodu zrodlowego...\n");

    while ((c = wezZnak()) != EOF) {    /* pobieranie kolejnych znakow do konca pliku */

        switch(c) {

        case ' ': case '\t':    /* spacja lub tabulacja */
            wypiszBufor();
            if (pierwszyZnakWiersza == 1 && srednik == 0)
                fprintf(f2, "%c", c);
            break;

        case '\n':    /* znak nowego wiersza */
	    wypiszBufor();
            if (srednik == 1)
                fprintf(f2, "\n");
            if (pierwszyZnakWiersza == 0)  /* omin jesli nie bylo jeszcze czarnego znaku */
                break;
            pierwszyZnakWiersza = 0;
            if (srednik == 0)
                fprintf(f2, "\n");
            srednik = 0;
            break;

        case ';':
            wypiszBufor();
            if (pierwszyZnakWiersza == 0)
                zrobWciecie(wciecie);
            fprintf(f2, ";");
            srednik = 1;
            wciecieStare = wciecie;
            /* po sredniku wychodzi o odpowiednia liczbe poziomow z zaglebienia wciecia */
            while (wcieciaTab[wciecie] == 1 || wcieciaTab[wciecie] == 3)
                wciecie--;
            break;

        case '{':
            wypiszBufor();
            if (srednik == 1) {
                srednik = pierwszyZnakWiersza = 0;
                fprintf(f2, "\n");
            }
            if (pierwszyZnakWiersza == 1)  /* jesli w wierszu byl juz czarny znak to wypisz \n */
                fprintf(f2, "\n");
            zrobWciecie(wciecie);
            fprintf(f2, "{\n");
            pierwszyZnakWiersza = 0;
            wcieciaTab[++wciecie] = 2;     /* zwieksza wciecie
					    * i zapisuje co spowodowalo wciecie (tu {) */
            break;

        case '}':
            wypiszBufor();
            if (pierwszyZnakWiersza == 1)  /* jesli w wierszu byl juz czarny znak to wypisz \n */
                fprintf(f2, "\n");
            zrobWciecie(--wciecie);  /* wychodzi o jeden poziom z zaglebienia we wcieciu */
            fprintf(f2, "}\n");
            wciecieStare = wciecie + 1;
            /* wychodzi o odpowiednia liczbe poziomow z zaglebienia wciecia */
            while (wcieciaTab[wciecie] == 3)
                wciecie--;
            srednik = pierwszyZnakWiersza = 0;
            break;

        case '\"': case '\'':    /* poczatek napisu lub znaku */
            wypiszBufor();
            cp = c;
            k = cl = 0;
            fprintf(f2, "%c", c);
            do {
                cl = c;
                fprintf(f2, "%c", c = wezZnak());  /* pobranie znaku z wejscia i wypisanie
						    * go na wyjscie
						    */
                if (c == '\\')
                    k = (cl == '\\' && k == 1 ? 0 : 1);  /* ustawienie "licznika" backslashy */
            } while (c != cp || (cl == '\\' && (cl != '\\' || k != 0)));
	    /* ... wykonuj gdy nie zakonczyl sie jeszcze napis "napis" lub 'znak' */


            break;

        case '/':    /* ewentualny poczatek komentarza */
            oddajZnak(cp = wezZnak());  /* podejrzyj kolejny znak z wejscia */
            oddajZnak('/');
            wypiszBufor();
            if (cp != '*') {  /* jesli kolejny znak nie jest * to ... */
                fprintf(f2, "%c", wezZnak()); /* wypisz slash na wyjscie i kontynuuj */
                continue;
            }
            oddajZnak(ominKomentarz());  /* wywolanie funkcji omijajacej komentarze */
            fprintf(f2, "\n");
            break;

        default:    /* inny znak nie obsluzony powyzej */
            if (spBuf >= MAX_BUF)  /* jesli bufor jest pelny to ... */
                wypiszBufor();     /* wypisz bufor na wyjscie i go oproznij */
            buf[spBuf++] = c;  /* wpisz otrzymany znak do bufora */

            if (srednik == 1) {  /* jesli ustawiona jest flaga "srednik" to ... */
                srednik = pierwszyZnakWiersza = 0; /* wyzeruj flagi i wypisz \n */
                fprintf(f2, "\n");
            }

            if (pierwszyZnakWiersza == 0) {  /* jesli w linii nie bylo czarnego znaku to ... */
                pierwszyZnakWiersza = 1;     /* ustaw flage czarnego znaku na 1
                                              * - wlasnie otrzymalismy czarny znak */
                zrobWciecie(wciecie);        /* zrob odpowiednie wciecie */
            }

	    cp = oddajZnak(wezZnak());  /* podejrzyj kolejny znak */

            /* gdy w buforze jest "else" a kolejny znak nie jest cyfra lub litera
	     * (wykluczamy dluzszy napis zaczynajacy sie od else - np. elsele2) */
	    if (!strcmp(buf, "else") && !isalnum(cp)) {
		int i;

                /* odszukiwanie if, do ktorego odnosi sie ten else */
                for (i = wciecieStare; i > 0 && coSpowodowaloWciecie[i] != 1; i--)
                    ;
                zrobWciecie(i - wciecie - 1);
                wypiszBufor();
		fprintf(f2, "\n");
                wciecie = i - 1;
		coSpowodowaloWciecie[wciecieStare] = 0;

                pierwszyZnakWiersza = 0;
                c = ominKomentarz();

		/* jesli znak inny niz { to zaznacz ze to nie poczatek bloku zrobil wciecie */
                if (c != '{')
                    wcieciaTab[++wciecie] = 3;

                pierwszyZnakWiersza = spBuf = 0;
                oddajZnak(c);

            /* gdy w buforze jest "for", "if", "while" albo "switch" a kolejny znak
	     * nie jest cyfra lub litera (wykluczamy dluzszy napis zaczynajacy sie od
	     * for, if, while albo switch - np. forever) */
            } else if ((!strcmp(buf, "for") || !strcmp(buf, "if") || !strcmp(buf, "while") ||
                       !strcmp(buf, "switch")) && !isalnum(cp)) {
                int i, nawiasy = 1;
                /* Po wszystkich z obslugiwanych tu instrukcji wystepuje wyrazenie
		 * objete nawiasami okraglymi - tu zajmuje sie ich ominieciem.
		 * Poniewaz wyrazenie moze miec wiele nawiasow nalezy je zliczac.
		 * Nalezy tu tez zwrocic uwage na komentarze i literaly napisowe i znakowe. */
		fprintf(f2, "%s", buf);
                c = ominKomentarz();
                fprintf(f2, "%c", c);
                do {
                    fprintf(f2, "%c", c = ominKomentarz());
                    if (c == '\'' || c == '\"') {  /* jesli poczatek napisu lub znaku */
                        cp = c;
                        k = 0;
                        do {
                            cl = c;
                            fprintf(f2, "%c", c = wezZnak());
                            if (c == '\\')
                                k = (cl == '\\' && k == 1 ? 0 : 1);
                        } while (c != cp || (cl == '\\' && (cl != '\\' || k != 0)));
			/* ... do konca napisu/znaku */

		    } else if (c == '(')
                        nawiasy++;  /* zwieksz licznik nawiasow */
                    else if (c == ')')
                        nawiasy--;  /* zmniejsz licznik nawiasow */
                    else if (c == '\n')
                        zrobWciecie(wciecie);
                } while (nawiasy > 0);  /* wykonuj jesli nie domkniete ostatniego nawiasu */

                /* co kolejne po parametrach ... */
                i = pierwszyZnakWiersza;
                pierwszyZnakWiersza = 0;
                c = ominKomentarz();
                pierwszyZnakWiersza = i;

                /* jesli obslugujemy tu "if" to zaznaczam ten fakt */
                coSpowodowaloWciecie[wciecie + 1] = (!strcmp(buf, "if")) ? 1 : 0;
		
                if (c != '{') {
                    wcieciaTab[++wciecie] = 3;
                    pierwszyZnakWiersza = 0;
                    fprintf(f2, "\n");
                }
                oddajZnak(c);
                spBuf = 0;
		memset(buf, 0, MAX_BUF);

            }
            break;
        }
    }

    printf("Koniec.\n");
    return 0;
}

/* Funkcja wyprowadza na wyjscie odpowiednia dla wciecia liczbe spacji. */
void zrobWciecie(int n)
{
    int i;

    for ( ; n > 0; n--)
        for (i = 0; i < SZEROKOSC_WCIECIA; i++)
            fprintf(f2, " ");
}

/* Funkcja zatrzymuje sie gdy napotka znak
 * nie bedacy czescia komentarza - komentarze
 * sa wypisywane na wyjscie.
 * Zwraca pierwszy napotkany znak nie bedacy
 * czescia komentarza.
 */
int ominKomentarz(void)
{
    int c, cl, cp, k = 0;

    while ((c = wezZnak()) != EOF) {	    

        if (c == '/' && (cp = oddajZnak(wezZnak())) == '*') {  /* jesli poczatek komentarza */
            if (k)
                fprintf(f2, "\n");
            k = 1;
            if (pierwszyZnakWiersza == 0) {  /* wciecie dla kolejnych wierszy komentarza */
                pierwszyZnakWiersza = 1;
                zrobWciecie(wciecie);
            }
            fprintf(f2, "/*");
            wezZnak();
            c = 0;
            /* wypisywanie komentarza na wyjscie */
            do {
                cl = c;
                fprintf(f2, "%c", c = wezZnak());
                if (c == '\n') {  /* wciecie dla kolejnych wierszy komentarza */
                    while (isspace(c = wezZnak()))        /* omin biale znaki */
                        ;
                    zrobWciecie(wciecie);
                    fprintf(f2, "%c", c);
                }
            } while (c != '/' || cl != '*'); /* do konca komentarza */
            pierwszyZnakWiersza = 0;
        } else if (!isspace(c)) {
            return c;
	} else {
            if (pierwszyZnakWiersza)
                fprintf(f2, "%c", c);
            if (c == '\n')
                pierwszyZnakWiersza = 0;
        }
    }
    return c;
}

/* Funkcja wypisuje na wyjscie zawartosc bufora. */
void wypiszBufor(void)
{
    fprintf(f2, "%s", buf);   /* wypisanie bufora na wyjscie */
    memset(buf, 0, MAX_BUF);  /* czyszczenie bufora */
    spBuf = 0;                /* zerowanie znacznika poczatka bufora */
}

#define MAX_ZNAKOW 100    /* maksymalna liczba znakow w buforze */
char bufZnakow[MAX_ZNAKOW];
int bufp = 0;

/* Funkcja zwraca znak z bufora wejsciowego. */
int wezZnak(void)
{
    return (bufp > 0) ? bufZnakow[--bufp] : fgetc(f1);
}

/* Funkcja wpisuje znak do bufora wejsciowego
 * (w rzeczywistosci znaki przechowywane sa w tablicy).
 */
int oddajZnak(int c)
{
    if (bufp >= MAX_ZNAKOW) {  /* jesli w buforze na zwroty znakow nie ma juz miejsca to ... */
        fprintf(stderr, "oddajZnak: przepelnienie bufora!\n");
        return EOF;
    }
    return bufZnakow[bufp++] = c;
}
w3cw3c
automatyka przemysłowa