Parser: identyfikatory, symbole i ich indeksy 21044 6

O temacie

Autor inż. Avallach

Zaczęty 1.03.2011 roku

Wyświetleń 21044

Odpowiedzi 6

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator

inż. Avallach
Administrator

Parser: identyfikatory, symbole i ich indeksy
2011-03-01, 17:57(Ostatnia zmiana: 2014-04-17, 21:43)
Jednym z kluczowych elementów skryptów są identyfikatory. To nazwy oznaczające jakieś byty w kodzie - klasy, prototypy, funkcje, zmienne, stałe. Wszystkie te rzeczy, po wywaleniu których moglibyśmy dostać dobrze znany komunikat "Unknown identifier", o ile były gdzieś używane.
Problemem osób piszących skrypty w Daedalusie jest to, że zwykle nie odróżniają identyfikatorów od obiektów na które wskazują. Na pytanie czym jest "pc_hero", większość odpowiedziałaby pewnie że obiektem klasy c_npc. Niestety nie jest to do końca prawda. To identyfikator który na taki obiekt wskazuje w trakcie gry. Ta różnica jest ważniejsza niż może się wydawać, o czym można łatwo przekonać się próbując dokonać przypisania jednego do drugiego:
var c_npc bezimienny; bezimienny = pc_hero; Taki kod spowoduje otrzymanie komunikatu o niezgodności typów. Aby zrozumieć o co chodzi, trzeba zapoznać się z tym co parser robi z identyfikatorami.

Parser przetwarzając skrypt, na podstawie każdego napotkanego identyfikatora tworzy nowy obiekt klasy zCPar_Symbol i zapisuje wskaźnik na niego pod kolejnym indeksem w tablicy zCParser.symtab_table_array. Pełną definicję klasy zCPar_Symbol z opisem można obecnie znaleźć w Ikarusie, wygląda ona w dużym skrócie tak:
class zCPar_Symbol
{
    var string name;       // identyfikator
    var int content;         // zawartość lub wskaźnik na zawartość
    var int offset;            // wskaźnik związany z zawartością
};
Przykładowo, zakładając że parser przetworzył dotąd 1000 identyfikatorów i napotkał taki kod:
class JakasKlasa { };
func void JakasFunkcja (var int argumentFunkcji) { var string zmiennaLokalna; };
instance JakisNpc (C_NPC) { };
const int stalaGlobalna = 42;
Stworzy kolejne symbole zapisze je pod odpowiednimi indeksami:
JakasKlasa -> 1001
JakasFunkcja -> 1002
JakasFunkcja.argumentFunkcji -> 1003
JakasFunkcja.zmiennaLokalna -> 1004
JakisNpc -> 1005
stalaGlobalna -> 1006
i tak dalej.
Symbole różnią się od siebie w zależności od tego co oznaczały, w przypadku większości rodzajów symboli można odczytać ich indeksy... tak po prostu:
print(IntToString(pc_hero));W praktyce wszyscy to robią, po prostu zwykle nie zwracają uwagi na to że to czym operują to właściwie liczby, a nie obiekty. Przykładem są wszelkie wywołania funkcji Hlp_GetNpc czy CreateInvItems. Obie jako argumenty przyjmują liczby (indeksy symboli) - można to łatwo sprawdzić w ich deklaracji.
W przypadku symboli instancji, istnieje też elegancki sposób na otrzymanie ich indeksów - funkcja Hlp_GetInstanceID.

Symbole oprócz swoich numerów i identyfikatorów, mają też zawartość. Będzie ona różnego rodzaju w zależności od tego co symbol reprezentuje. 
Stałe i zmienne:
 - content będzie zawierało wartość lub wskaźnik na wartość
 - offset będzie zawierało przesunięcie wewnątrz klasy, o ile dana zmienna należy do jakiejś klasy
Funkcje:
 - content będzie zawierało wskaźnik na kod na stosie
 - offset będzie zawierało wskaźnik na wynik
Prototypy (są w pewnym sensie funkcjami inicjalizującymi nowy obiekt wybranym zestawem danych):
 - content będzie, tak jak przy funkcjach, zawierało wskaźnik na kod na stosie
Instancje są specyficznym przypadkiem. Mają trochę ze zmiennych i prototypów, ale oba ich składniki są opcjonalne i mogą być puste
 - content może zawierać wskaźnik na kod inicjalizujący (jeśli istnieje) (jak przy prototypach)
 - offset może zawierać wskaźnik na obiekt stworzony na ich podstawie (zwykle ostatni) (jeśli istnieje) (trochę jak przy zmiennych)

Przykładowo:
instance jasio (c_npc) { name = "Jan"; };
Zakładając że nie stworzymy tego npc w grze, symbol o nazwie "jasio" będzie zawierał wskaźnik na kod inicjalizacyjny w polu content, ale pole offset będzie zawierało null pointera (bo nie istnieje żaden obiekt stworzony na podstawie tej instancji).

instance self (c_npc);
Taka deklaracja rzeczywiście znajduje się w constants.d. W przeciwieństwie do jasia, self nie posiada kodu inicjalizacyjnego - więc pole content będzie zawierało null pointera. Z drugiej strony, silnik przypisuje mu jako zawartość npc na których operuje - więc offset będzie zawierało wskaźnik na npc z którym ostatnio coś się działo.

instance nikt (c_npc);
Ten tutaj z kolei, będzie miał null pointery zarówno w content, jak i offset - nie ma funkcji inicjalizacyjnej i nie istnieje też żaden obiekt stworzony na podstawie tej instancji.

Normalnie nie mamy bezpośredniego dostępu do zawartości symboli, ale funkcje zewnętrzne którym podamy je jako argumenty, będą próbowały uzyskać do nich dostęp.
Przykładowo, Wld_InsertNpc oczekuje jako argumentu indeksu symbolu oznaczającego obiekt klasy c_npc. Z założenia jednak nie oczekuje ona że taki obiekt już istnieje, więc nie próbuje odczytać tego na co wskazuje "offset". Zamiast tego tworzy nowy obiekt i w jego kontekście wywołuje kod na który wskazuje wskaźnik w "content". Można to sprawdzić wywołując tą funkcję i podając self jako argument. Ponieważ ta instancja nie ma kodu inicjalizacyjnego, powinniśmy otrzymać crash. Z kolei podając jako argument naszego jasia, powinno obejść się bez problemów, pomimo że wcześniej nie istniał obiekt na który ten symbol mógłby wskazywać.

Przykładem odwrotnego zachowania jest funkcja Npc_IsDead. Ona z kolei nie potrzebuje tworzyć nowego npc, a jedynie odczytać wskaźnik na obiekt już istniejący. Dlatego możemy bez crasha wywołać Npc_IsDead(self). Wywołanie jej z nigdy nie stworzonym jasiem mogłoby spowodować crash, ale tego akurat nie jestem pewien.

Odkąd napisałem ten artykuł w 2011, dwa razy przepisywałem jego treść. Obecna wersja zawiera informacje zawdzięczane Sektenspinnerowi - cały kawałek o zawartości symboli. To co ja pierwotnie zauważyłem i o czym napisałem, to samo odróżnianie obiektów od identyfikatorów i posługiwanie się indeksami symboli :D

Cedric

Cedric

Użytkownicy
posty782
Propsy1084
Profesjabrak
  • Użytkownicy
Dziękuję mistrzowi Avallachowi za cenną wiedzę  :ok:
 

Adanos

Adanos

Administrator
Szara eminencja
posty5204
Propsy3870
ProfesjaProgramista
  • Administrator
  • Szara eminencja
var c_npc enpec; enpec = NONE_100_Xardas;Jak powszechnie wiadomo, zostanie wywalony błąd, choć mało kto próbuje dochodzić tej przyczyny.

var c_npc enpec; enpec = NONE_100_Xardas;
enpec = hlp_getnpc(enpec);
Druga funkcja także zwróci jak najbardziej prawidłowy wynik, mimo że tutaj zamiast prawidłowego argumentu jakim byłby indeks symbolu, podaliśmy przed chwilą otrzymane odniesienie do obiektu.

Popraw to, bo coś się chyba nie zgadza...

Spoiler
BTW Nieźle nakombinowałeś z tym tematem :lol:

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator
Aha, racja, nie dopisałem hlp_getnpc. Poprawione:
var c_npc enpec; enpec = hlp_getnpc(NONE_100_Xardas);
enpec = hlp_getnpc(enpec);

Spoiler
Tu był mój stary tutek o tym mechanizmie, ale że temat został przerobiony od nowa, napisałem to od początku, uwzględniając nowe informacje :D
Wcześniej, jak się okazuje raczej mylnie, wiązałem te indeksy symboli z indeksami npc'ów.

inż. Avallach

inż. Avallach

Administrator
posty7662
Propsy5238
NagrodyV
ProfesjaProgramista
  • Administrator
Przeglądając WoG znalazłem to:
Cytat: NicoDE
Weil der "Parser" automatisch den Symbolindex als Parameter übergibt, wenn ein Integer gefordert und eine Objektreferenz übergeben wird.
In deinem Fall wurde also der Index von "self" übergeben. Und die External sucht nun nach einem NPC mit dem Objektnamen "SELF"
Cytat: Lehona
Wenn du eine Klassen-Variable (C_NPC, C_ITEM...) angibst, wird automatisch Code erzeugt zum "Umwandeln" in einen Symbolindex. Wenn du Hlp_GetNpc(self); angibst wird der Parser z.B. meckern.
Praktycznie nie rozumiem niemieckiego (Google Translate strasznie tutaj nie pomaga) ale chyba to jest na ten temat :D

Lehona

Lehona

Użytkownicy
posty196
Propsy190
  • Użytkownicy
Maybe just to clarify: A symbol index refers to a certain symbol in the symbol table (who'd have guessed?). This, in the case of an object reference such as 'var c_npc slf', stores the address of the object in zCPar_Symbol.offset, zCPar_Symbol.content will be empty I think (might be wrong). So the parser will either push it as an instance or as an integer. The only difference lies in the 'opcodes' used. I won't go into further detail because there is no tutorial here on how the data stack works and I don't want to explain all of it :p If anyone needs to know I can elaborate.
 
Unless specified otherwise, my posts are always about Gothic 2 Night of the Raven.



0 użytkowników i 1 Gość przegląda ten wątek.
0 użytkowników
Do góry