Translator własnego języka do batch - teoretyka.

0

Witam, robię tłumacz własnego języka na język skryptowy Batch. Ale największy problem sprawia mi rozkład języka na "części pierwsze". Może mi ktoś podpowiedzieć w jak sposób najlepiej rozkładać kod?

Postanowiłem że w języku będą zawarte Metody oraz Słowa Kluczowe. Metody będą zawierały wszystkie funkcje języka batch, np. czyszczenie ekranu, wypisywanie czegoś na ekranie itp. Skład polecenia będzie prosty: Metoda{ARG1, ARG2, etc} np: Print{'Tekst'}

Natomiast słowa kluczowe to będą bardziej złożone funkcje, np: instrukcja warunkowa, pętla, zmienne. Sposób byłby troszkę inny: If {WARUNEK operator WARUNEK} lub Var Nazwa_zmiennej = Wartość.

Wydaje mi się że rozkład języka na części jest najtrudniejszy, dlatego zwracam się tutaj o pomoc.

Na razie zrobiłem coś takiego:

unit Parser;

interface

uses
  Classes,
  System.SysUtils,
  API_Lvl_1;

function Parse(SL : TStringList) : TStringList;
function Method(SL : TStringList) : String;
function Keyword(SL : TStringList) : String;

implementation

function Parse(SL : TStringList) : TStringList;
var
  Code_Line : TStringList;
  I : Integer;
begin
  Result := TStringList.Create;
  Code_Line := TStringList.Create;
  for I := 0 to SL.Count-1 do
  begin
    if not SL.Strings[I].IsEmpty then
    begin
      if SL.Strings[I].Contains('{') then if SL.Strings[I].Contains('}') then
      begin
        ExtractStrings(['{', ',', '}'], [' '], PWideChar(SL.Strings[I]), Code_Line);
        Method(Code_Line);
        Code_Line.Clear;
      end;
      if not SL.Strings[I].Contains('{') then if not SL.Strings[I].Contains('}') then
      begin
        ExtractStrings([' '], [' '], PWideChar(SL.Strings[I]), Code_Line);
        Keyword(Code_Line);
        Code_Line.Clear;
      end;
    end;
  end;
end;

function Method(SL : TStringList) : String;
begin
  if SL.Strings[0].ToLower = 'print' then Result := API_Lvl_1.Method_Print(SL);
end;

function Keyword(SL : TStringList) : String;
begin
  if SL.Strings[0].ToLower = 'var' then Result := API_Lvl_1.Keyword_Var(SL);
end;


end.

Powyższy kod analizuje wczytany wcześniej do zmiennej plik tekstowy, oraz zwraca już przetłumaczony tekst głównej części programu, gdzie zostanie zapisany do pliku.

0

Nie wiem czy dobrze doradzę, bo nigdy nie bawiłem się w pisanie własnego interpretera. Jednak jeżeli piszesz pod VCL, to może wspomóż się czymś takim jak zestawem komponentów RemObjects Pascal Script. Pofrafią one wykonywac wklepany kod. Dodawać własne elementy oraz wiele innych. I właśnie operują na składni (obiektowego) Pascala. A to zdaje się chcesz osgiągnąc. Odeszło by Tobie sprawdzanie poprawności i takie tam, bo to realizują moduły tej paczki komponentów.

Do tego posta dołaczam załącznik, z pobraną kiedyś wersją tej paczki i najpotrzebniejszymi modułami. Pisane i kompilowane pod Delphi 7. Jak widzisz - jest tam dołaczona przykładowa - jedyna obsługiwana procedura ShowMsg. Ty natomiast mozesz sobie dopisać własne. I na przykład w zmiennej klasy formularza albo globalnej dopisywać do na przykład TStringList kolejne polecenia przetłumaczone na batch. Nie wiem czy to Ci wiele pomoże. Ale ja bym pokombinował w ten spsoób. Ewentualnie za pewne ktoś jeszcze, bardziej doświadczony coś Tobie tutaj doradzi.

0

Dzięki za odpowiedź.
Już kiedyś widziałem to, ale sam się tym jeszcze nie zajmowałem. Jednak chodzi mi o coś troszkę innego.
Kod będzie zapisywany w pliku tekstowym, a tłumacz będzie konsolową aplikacją(bez IDE). W momencie gdy uruchomię ten tłumacz z parametrem zawierającym ścieżkę do pliku źródłowego, sprawdzi kod, przetłumaczy i zapisze do pliku *.bat. Zależy mi na utworzeniu własnego języka bo dzięki temu mogę podszkolić swoje własne umiejętności.

Twoje rozwiązanie jest dobre, nawet bardzo dobre, tylko chciałbym aby to było interpretowane przez cmd.

0

Nic nie stoi na przeszkodze aby załadować plik podany jako ParamStr, jeżeli istnieje. A następnie operować tym zestawem komponentów. Wiadomo, opasły programik do operowania tylko na konsoli - trochę lame. Ale coż, cięzko trochę rzeźbić z tym xzestawem kontrolek aby przepisać go do czystego WinAPI. Sam wiem jakie to męki, ze źródeł VCL przenieść najważniejszy dla nas kod, żeby na przykład działał jakis moduł, nawet bez TStrings i TException. Ale teraz mało kto, poza tymi co kiedyś jedli crackersy zwracają uwagę na to że ich twór powinien być w masmie i WinAPI ;) Ja prefetuje WinAPI, jeśli idzie ogarnąć :)

Także napisz to sobie jako konsolówkę z użyciem tych modułów. Myślę, że jeżeli w konstruktorze TPSScript podasz po prostu nil, to też się wszystko uda ogarnąc. Nie jest to komponent wizualny, a więc nie wymaga Parent co widać w moim kodzie zresztą. Gdyż jak może wiadomo, jeżeli dostarczam jakiś kod, to staram się aby zewnętrzne moduły były w miarę możliwości dołaczone, a komponenty tworzone dynamiczne. Tak by chcąc sprawdzić efekt działania, nie trzeba było ich instalować itp.

0

Popytaj @Patryk27 - doradzi Ci w jaki sposób parsować kod i przerabiać na inny bo póki co jeszcze w tym siedzi; Ale w tym wypadku bez tokenizera raczej się nie obejdzie; No i przechowywanie kodu w liście raczej nie jest najlepszym pomysłem - lepiej było by mieć ten kod w zmiennej typu String; Dało by to większe możliwości parsowania;

No chyba że chcesz ten kod parsować linia po linii (bo taka jest struktura kodu), to ewentualnie lista mogła by zostać.

1

Zacząłbym od tego, że potrzebujesz leksera z prawdziwego zdarzenia - zobacz sobie, jak ja napisałem swój: https://github.com/Piterolex/SScript-Compiler/blob/master/parser/lexer/lexer.pas (oczywiście moja wersja jest napisana dosyć "naiwnie", lecz imho czytelnie).

Lekser to po prostu takie "coś", co z kodu (ciągu znaków) stworzy listę tokenów, np.:
Z kodu:if (x == true)
Otrzymamy:

keyword_if: if
left_parenthesis: (
identifier: x
operator_equal: ==
identifier: true
right_parenthesis: )

Jak napiszesz jakiś konkretny (a nie jest to trudne), dopiero wtedy będziemy mieli coś, na czym będzie się dało stworzyć "translator" (czyli fachowo kompilator) i będziemy mogli mówić o czymś więcej.

0

Pobrałem sobie twój lexer, lecz zanim zrozumiem go, zajmie mi to trochę, bo dla mnie to na razie czarna magia :D
Może mógłbyś mi wyjaśnić w jaki sposób on działa?

1

Akurat rozumienie mojego kodu to wyższy poziom abstrakcji - ogólnie leksery (inaczej zwane tokenizerami) działają na zasadzie czytania tekstu znak po znaku i sprawdzania, co zostało napotkane.

Przykładowo: if (x == 10), lekser przetworzyłby jako (w metodzie typu TLexer.getNextToken()):

Wywołanie 1:
1.i -> ok, mamy i, czyli czytamy identyfikator*.
2.f -> łącznie odczytaliśmy już if, idziemy dalej.
3. -> oh, spacja! To już nie jest znak identyfikatora (nie jest alfanumeryczna), zaprzestajemy dalszego czytania i zgłaszamy, że token to identyfikator, a dokładniej słowo kluczowe: if. Czekamy na następne wywołanie getNextToken().

Wywołanie 2:
1. -> pomijamy spację, ponieważ nie jest ona żadnym tokenem (nic nie znaczy**).
2.( -> o, napotkaliśmy nawias. Nawias sam w sobie jest tokenem, zatem zgłaszamy, że aktualny token to lewy nawias (u mnie bajerancko nazwany _BRACKET1_OP, patrz: https://github.com/Piterolex/SScript-Compiler/blob/master/parser/lexer/tokens.pas) i zaprzestajemy dalszego czytania.

Wywołanie 3:
1.x -> mamy znak z alfabetu, więc czytamy całość jako identyfikator.
2. -> o, spacja. Zatem zgłaszamy, że aktualny token to identyfikator, a dokładniej identyfikator "x". Czekamy na następne wywołanie.

Wywołanie 4:
1.= -> o, znak równości. Ale mamy taki problem, że są dwa operatory podobne do siebie: = (przypisanie) oraz == (równość). Aby rozstrzygnąć, który z nich jest tym, co właśnie czytamy, sprawdzamy następny znak.
2.= -> kolejny znak równości. Zatem aktualny token to równość: ==. Koniec czytania, czekamy na następne wywołanie.
^ gdyby następnym znakiem była np.spacja, litera, cyfra (...), zgłaszamy, że aktualny token to = i czekamy.

Wywołanie 5:
1.1 -> czyli mamy do czynienia z liczbą, czytamy dalej...
2.0 -> łącznie mamy już 10, dalej...
3.) -> oh, nawias zamykający. Nawias już nie jest częścią liczby, zatem zgłaszamy, że aktualny token to liczba całkowita: 10 i czekamy na następne wywołanie.

Wywołanie 6:
1.) -> nawias zamykający jest tokenem sam w sobie, więc raportujemy go jako nawias zamykający i czekamy na kolejne wywołanie.

Wywołanie 7:
1.Koniec pliku/kodu/ciągu znaków/cokolwiek - zgłaszamy end of file, koniec procesu tokenizowania.


`*` "identyfikator" to tutaj "ciąg dowolnej długości zaczynający się literą i składający się ze znaków alfanumerycznych" `**` "nic nie znaczy" w moim języku - co innego, gdyby chodziło np.o Whitespace (chociaż tam raczej rzadko korzysta się z tokenizerów ;p).
0

A co jeśli po jakiejś instrukcji, jest blok innych instrukcji? Np.

if (x == 10) (
  Kod
)
1

Lekser się tym nie przejmuje, pomijasz znaki nowej linii (chyba że w Twoim języku są istotne) i idziesz dalej:

słowo kluczowe: if
lewy nawias
identyfikator: x
operator równości
liczba: 10
prawy nawias
lewy nawias
<jakiś kod>
prawy nawias
1

Myślę, że łatwiej ci będzie to wszystko ogarnąć gdy poczytasz sobie o językach formalnych i gramatykach. Ciebie powinny zainteresować gramatyki bezkontekstowe. Nie mówię tutaj o rypaniu jakiś regułek i dziwnych wzorków tylko ogólne rozeznanie w temacie. Pomoże ci to zorganizować dobrze pracę nad tego typu projektami. Będziesz wiedział od czego zacząć i jak wszystko ze sobą powiązać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1