Pomoc przy programi formatującym kod.

0

Witam, po raz pierwszy!

Chciałbym prosić was o pomoc w programie w Pascalo, który właściwie już napisałem, przy czym nie jest on optymalnych rozmiarów, ani też nie działa optymalnie(nie dla wszystkich przypadków).

Treść: Program, który zapewni poprawne formatowanie pliku tekstowego napisanego w języku Pascal. Plik źródłowy jak i plik wyjściowy powinny być podane jako argumenty programu.

i przykład dla języka C:

Wejście:
int main(int argc, char* argv[]){printf(“Hello world!\n”);return0;}

Wyjście:
int main(int argc, char* argv[])
{
printf(“Hello world!\n”);
return 0;
}

Mój program, skończony, ma bagatela 926 linijek, ale jestem przekonany, że źle podszedłem do tego problemu + napotkałem wiele problemów i przez to sprawdzam w różnych miejscach to samo. Prosiłbym o jakieś wskazówki jak najlepiej ten problem rozwiązać, bo mój program mimo swojej długości dalej nie jest idealny i są przypadki, w których nie sformatuje pliku tak jakbym chciał (dla bardziej złożonych plików). Robię to na zasadzie przepisywania z pliku do pliku i w momencie wystąpienia pewnych instrukcji daję enter i ewentualnie dodaję odpowiednią ilość spacji do puli, którą potem przy np. end; zmniejszam. Z góry dziękuję za pomoc.

1

Zamiana pliku na tokeny i na ich podstawie wstawiać entery, spacje itp.
Np.po ; dać nową linię, po begin zwiększyć poziom spacji, po end zmniejszyć, przed i po := dać spację, itp.

0

Dzięki za odpowiedź : ). A jak jest z endem? Przy endzie właściwie trzeba by zmniejszyć poziom spacji przed i po jego wypisaniu, mam rację? I jeszcze pytanie jak to jest z plikiem w małej części sformatowanym, w sensie, że po np. := w pliku wejściowym jest już spacja, albo, ze po jakimś var jest spacja, jak to uwzględnić?
I jeszcze pytanie natury technicznej, wczytywać kolejne linijki do stringów? Słowa, po których są różna operacje, np te po których są same entery i te po których dodajemy spacje mają być w osobnych plikach tekstowych, tak?
Jakaś pomocna funkcja na wykonanie tego odczytywania i przepisywania tekstu po linijce i sprawdzania słów z plikami tekstowymi?

1
maslopi napisał(a):

Przy endzie właściwie trzeba by zmniejszyć poziom spacji przed i po jego wypisaniu, mam rację?

Chcesz formatować kod w stylu:

Program foo;
Procedure Xyz;
 Begin
  if (asd) Then
   Begin
    Writeln('asd');
   End;
 End;

(...)

Raczej zwyczajowo przyjęło się, że wszelkie begin czy end jest na "wysokości" if'ów i innych konstrukcji.

maslopi napisał(a):

np. := w pliku wejściowym jest już spacja, albo, ze po jakimś var jest spacja, jak to uwzględnić?

Ominąć je, Ciebie interesują jedynie konkretne tokeny (identyfikatory, operatory, liczby itd.), a w Pascalu spacja nim nie jest.

maslopi napisał(a):

Słowa, po których są różna operacje, np te po których są same entery i te po których dodajemy spacje mają być w osobnych plikach tekstowych, tak?

Tzn.? Chcesz trzymać te dane poza programem?

0

Ok w takim razie begin i end zostanie na wysokości if'ów, będzie właściwie trochę prościej. Co do tych słów, no to wydaje mi się, że jest ich na tyle, że chyba lepiej jest je mieć w pliku tekstowym, tak?

Na jakiej zasadzie działają tokeny, jest funkcja, która nie czyta spacji at all? A jak będzie z tekstem w (' ') w tym przypadku chciałbym te spacje przepisać ; >. Przepraszam za głupie pytania, jestem newbie : >.

0

W załączniku przesyłam mój prosty parser kodu, jedynie trzeba by pododawać odpowiednie słowa kluczowe; obejrzyj sobie jak to działa.

0

No niestety nic z tego nie wyniosę. Nazwy i funkcje, których nie rozumiem a Lazarus mi tego nie kompiluje : >

1

No to inaczej; ideą tokenizera jest przeszukanie pliku pod kątem odpowiednich tokenów i zrobienie z nich np.tablicy.
Z kodu:

Program Xyz;
Begin
 if (2+2 = 4) Then
  Halt Else
  Exit(666);
End;

Można zrobić listę tokenów:

_program
identyfikator :: Xyz
_begin
_if
nawias_otwierający
2
dodać
2
równe
4
nawias_zamykający
_then
identyfikator :: Halt
_else
identyfikator :: Exit
(...)

(należy parsować każdy znak z pliku i sprawdzać czy pasuje do jakiegoś tokena+wyjątki typu liczby wielocyfrowe oraz ciągi znaków).
I potem właśnie kod w formie tokenów zamienić na sformatowany kod wyjściowy (jest to lepsze od bawienia się funkcjami typu copy na stringach, bo nie wyłoży się na co większych plikach).

0

Czyli mając listę tokenów, najpierw sprawdzam czy w całym tekście jest pierwszy token, potem przechodzę do kolejnego? Inaczej... mając załóżmy słowa kluczowe: begin i repeat, chcę je przepisać i dać po nich enter + 2 spacje, jak to wygląda za pomocą tokenizera?

0

Mamy jakąś zmienną trzymającą poziom aktualnego zagłębiania blokowego (tj.ile spacji dać przed instrukcją): BlDeep (domyślnie równa zero).
Przyjmując, że przeparsowaną z kodu listę tokenów mamy w tablicy o nazwie TokenList otrzymujemy coś w rodzaju:

Var I, Q: Integer;
{...}
For I := Low(TokenList) To High(TokenList) Do
 Case TokenList[I].Token of
  _BEGIN: Begin // "begin" -> zwiększamy poziom zagłębienia o jeden i wypisujemy "begin" do pliku
           Inc(BlDeep);
           OutputWrite('begin');
           NewLine;
          End;
  _END: Begin // "end" -> zmniejszamy poziom zagłębienia o jeden i wypisujemy "end" do pliku
         Dec(BlDeep);
         OutputWrite('end');
         NewLine;
        End;
  _SEMICOLON: Begin // średnik; wypisujemy go do pliku, dajemy nową linię i dopełniamy spacjami (już tę nową linię)
               OutputWrite(';');
               NewLine;
               For Q := 1 To BlDeep Do // dopełniamy spacjami
                OutputWrite(' ');
              End;
  Else Begin // cała reszta
         OutputWrite(TokenList[I].String);
        End;
 End;

OutputWrite(string) dodaje tekst do pliku wyjściowego.
NewLine dodaje nową linię od pliku wyjściowego.
I w ten sposób napisaliśmy prosty formatter kodu ;)

0

a co znaczy _ przed każdym tokenem?

0

begin oraz end to są słowa kluczowe, a że potrzebujemy jakoś się orientować w tych tokenach, to ja preferuję dodawanie _ przed nimi.
Np.mamy taki typ oznaczający pojedynczy token:

Type TToken = (
_BEGIN,
_END,
_NUMBER,
_IDENTIFIER,
{...}
);

Bez tego underscore'a, begin byłoby wykrywane jako słowo kluczowe, a nie identyfikator (w FPC można by co prawda dodawać &, no ale...).

0

To takie kilka pytań:

  • Skąd co dla outputWrite jest plikiem wyjściowym
  • Case TokenList[I].Token of - co znaczy .Token
  • na jakiej zasadzie działa to porównywanie skoro '_' nie ma znaczenia (nie patrzy na tą podłogę?)
  • i cóż to jest semicolon?

edit. rozumiem już czemu podłoga musi być, ale czemu przy porównywaniu program wie, że ma jej nie czytać?

1
maslopi napisał(a):
  • Skąd co dla outputWrite jest plikiem wyjściowym
  • Case TokenList[I].Token of - co znaczy .Token
  • na jakiej zasadzie działa to porównywanie skoro '_' nie ma znaczenia (nie patrzy na tą podłogę?)
  • i cóż to jest semicolon?

Ad.1 - no, program zasadniczo operuje na dwóch plikach: jeden to wejściowy (z niesformatowanym kodem pas), a drugi to wyjściowy (ten, który już będzie sformatowany). OutputWrite(xyz) robi ni mniej ni więcej, ile Write(PlikWyjsciowy, xyz).
Ad.2 - ja w swoim tokenizerze definiuję każdy token jako rekord:

 Type TToken_P = Record
                  Token           : TToken;
                  TokenName       : String;
                  Value           : String;
                  Display         : String;
                  Posi, Line, Char: Integer;
                 End;

Token to konkretny token (np._BEGIN, _END i tak dalej)
TokenName to nazwa tego tokenu
Value to wartość, np.jeżeli jest to liczba, to tam jest ona zapisana
Display to "wygląd" tego tokenu w kodzie; np.token _SEMICOLON to średnik i pole to będzie wynosić: ;
Posi to pozycja od początku pliku
Line to linia w której token został wykryty
Char to znak w linii w której token został wykryty
Ad.3 -

ale czemu przy porównywaniu program wie, że ma jej nie czytać?

Chyba nie rozumiem pytania albo Ty nie rozumiesz kodu, który Tobie podałem.
Mamy dwie główne części programu - tokenizer/skaner kodu, który zamienia kod na tokeny (http://4programmers.net/Forum/Newbie/207819-pomoc_przy_programi_formatujacym_kod_pascal?p=900636#id900636) oraz formatter, który go już formatuje, lecz nie korzystając z kodu w formacie "surowym" (czyli program xyz; begin cośtam end;), a z listy tokenów. Stąd musimy operować na tokenach, a nie na ciągach znaków.
Ad.4 - semicolon to średnik, angielskiego nie znasz?

0

ah, nie skojarzyłem semicolon'u, wybacz.
Ok, to wracając to twojego prostego tokenizera, dopełniasz spacjami tylko po średniku, nie powinniśmy przy każdym?
No i jak jest z break'ami przy case? Nie trzeba?
I jeszcze jak wygląda najprostsza funkcja, która zamienia kod na tokeny?

Przepraszam za nieogarnięcie i, że prawdopodobnie pytam kilka raz o to samo, dzięki za cierpliwość.

0

dopełniasz spacjami tylko po średniku, nie powinniśmy przy każdym?
No i jak jest z break'ami przy case? Nie trzeba?

To co podałem to tylko zalążek...

I jeszcze jak wygląda najprostsza funkcja, która zamienia kod na tokeny?

Nie ma takiej; musisz sobie sam napisać.
Mój wariant podałem Ci kilka postów wcześniej w załączniku.

0

No nic, dzięki za pomoc, spróbuję coś uszkrobać.

0

No nie dam rady tego rozwiązać tokenizerem, za dużo nowości. Postaram się po prostu za pomocą tablicy stringów.

0

Jedno pytanko. Jest opcja określenia nazwy zmiennej inną zmienną? np.
mam trzy tablice klucz1, klucz2, klucz3, klucz4. Jest opcja zamiast 4 pętli zrobić jedną pętle w pętli, gdzie iterator będzie zmieniał nazwę tablicy kluczi

1
maslopi napisał(a):

Jedno pytanko. Jest opcja określenia nazwy zmiennej inną zmienną? np.
mam trzy tablice klucz1, klucz2, klucz3, klucz4. Jest opcja zamiast 4 pętli zrobić jedną pętle w pętli, gdzie iterator będzie zmieniał nazwę tablicy kluczi

Możesz spróbować wskaźnikami.

0
assign(tekst2, wyjscie);
      rewrite(tekst2);

      read(tekst1, kodwejsciowy);
      for i:=low(slowaklucz1) to high(slowaklucz1) do
        begin
          kodwejsciowy:=stringreplace(kodwejsciowy, slowaklucz1[i], slowaklucz1[i]+#10#13, [rfReplaceAll]);
        end;
      for i:=low(slowaklucz2) to high(slowaklucz2) do
        begin
          kodwejsciowy:=stringreplace(kodwejsciowy, slowaklucz2[i], slowaklucz2[i]+#10#13, [rfReplaceAll]);
        end;
      for i:=low(slowaklucz3) to high(slowaklucz3) do
        begin
          kodwejsciowy:=stringreplace(kodwejsciowy, slowaklucz3[i], slowaklucz3[i]+#10#13, [rfReplaceAll]);
        end;
      kodwejsciowy:=stringreplace(kodwejsciowy, 'else', #10#13+'else', [rfReplaceAll]);

      write(kodwejsciowy);
      readln;

      write(tekst2, kodwejsciowy);
      close(tekst1);
      close(tekst2);

slowaklucz1/2/3 to tablice z wyrazami kluczowymi, mam problem jednak. Przy write(kodwejsciowy) wypisuje mi dobrze tekst z enterami, do pliku tekst2 natomiast wkleja niezmieniony tekst bez enterów, co jest nietak? Dzieki za pomoc

1

Nie #10#13, a #13#10.

0

woah, ur a genius my friend, dziękuje bardzo : )

0

Już mi się głupio robi prosząc jeszcze raz o pomoc, ale jakiś pomysł na to, jak w moim sposobie wstawiać odpowiednią ilość spacji? : |

Edit:

  slowaklucz1: array[1..3] of string = (';', 'then ', 'do ');
  slowaklucz2: array[1..3] of string = ('repeat', 'begin', 'case');
  slowaklucz3: array[1..4] of string = ('var', 'unit', 'uses', 'type');
  slowaklucz4: array[1..3] of string = ('end', 'until', 'case');  
0

Jest opcja na dopisanie czegoś do pliku z tym, że nie do jego końca? Postanowiłem przelecieć plik "zEnterowany" i zapisać do tablicy w jakiej linijce ile jest spacji, potrzebuję teraz jeszcze w jakiś sposób dopisać to do pliku na początku każdego wiersza. reset(plik) wydaje się, że nie pozwala mi na dopisywanie.

1

Bez haxiorskich procedur nie da się; może dane trzymaj w TStringList, a dopiero pod sam koniec (gdy już wszystko będzie gotowe) zapisuj do pliku?

0

hmm, z tym, że mam cały plik źródłowy w jednym stringu, w którym robie entery w odpowiednich miejscach, jak rozdzielić go na StringList? Ew. może dałoby się dodać te spacje przy przepisywaniu do kolejnego pliku tekstowego?

0

Bardzo prosiłbym o pomoc

0

jak rozdzielić go na StringList?

Spróbuj po prostu przypisać go do StringList.Text, powinno zadziałać (samo podzieli).

0

Rozumiem, że StringList to funkcja, tak? Czyli po prostu mój jeden string mam :=StringList.Text , gdzie Text to?

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.