PAWN Pre-Processor Część 2

Zawartość Część 1 – Obejmuje wprowadzenie do preprocesora oraz kilka ważnych rzeczy przydatnych podczas pisania makr. Część 2 – Wyjaśnienie dokładnie czego szuka kompilator oraz typowych zastosowań makr. Część 3 – Opis innych dostępnych dyrektyw (oprócz”#define”) oraz spojrzenie na definicje bez wartości podmiany. Część 4 – Używanie stringów w preprocesorze. Część 5 – Alternatywy dla preprocesora, wiele symboli i rekurencja. Część 6 – Problemy z makrami oraz spacje. Wyszukiwanie wzorców Najprostszym sposobem, aby wyjaśnić wszystkie wzorce jest opisanie czego dokładnie szuka kompilator. Należy też pamiętać że makra są tylko ( albo aż ) podmianą tekstu. Wszystkie poniżej są prawidłowe:
// Definition
#define MY_DEF                          IsPlayerConnected
// Use
if (MY_DEF(playerid))
// Output
if (IsPlayerConnected(playerid))
// Definition
#define MY_DEF                          IsPlayerConnected(
// Use
if (MY_DEF playerid))
// Output
if (IsPlayerConnected(playerid))
// Definition
#define MY_DEF                          IsPlayerConnected(playerid
// Use
if (MY_DEF))
// Output
if (IsPlayerConnected(playerid))
// Definition
#define MY_DEF                          IsPlayerConnected(playerid))
// Use
if (MY_DEF
// Output
if (IsPlayerConnected(playerid))
// Definition
#define MY_DEF                          if (IsPlayerConnected(playerid))
// Use
MY_DEF
// Output
if (IsPlayerConnected(playerid))
// Definition
#define MY_DEF(%0)                      if (IsPlayerConnected((%0)))
// Use
MY_DEF(playerid)
// Output
if (IsPlayerConnected(playerid))
Nie ma znaczenia czy kod jest poprawny przed podmianą , ważne jest czy jest poprawny po podmianie. Definicje Definicja poniżej składa się tylko z tekstu do podmiany i tekstu na który podmieniasz np. „MAX_PLAYERS”. Przykłady powyżej pokazują kilka różnych sytuacji i typów wzorców podmiany. Makra i definicje mogą zaczynać się od podanych znaków:
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
_ @
Dalej możemy używać takich znaków:
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
_ @
Makra Wzorzec wyszukiwania makra z parametrami ma bardzo prostą składnie składająca się z trzech części.
#define 
Część 1 jest to nazwa makra – jest to ta sama konstrukcja którą widzimy w definicjach i może używać tych samych symboli. Część 3 jest po prostu znakiem ( dowolnym ) – który kończy makro. Najczęściej jest to ’)’ – zamykający nawias ale może być to tak naprawdę wszystko. Część 2 jest tak naprawdę wszystkim innym i może zawierać parametry , litery i liczby i wszystkie inne losowe symbole. Ta część musi się zaczynać od błędnego znaku tak aby kompilator wiedział kiedy kończy się nazwa makra. Ta część zawiera wszystkie parametry. Przykłady – wszystkie poniżej są prawidłowymi wzorcami wyszukiwania ( poszczególne części zostały podzielone ):
// Part 1: MY_FUNC
// Part 2: (
// Part 3: )
#define MY_FUNC()
// Part 1: MY_FUNC
// Part 2: (%0
// Part 3: )
#define MY_FUNC(%0)
// Part 1: MY_FUNC
// Part 2: (%0-%1
// Part 3: )
#define MY_FUNC(%0-%1)
// Part 1: MY_FUNC
// Part 2: $%0,%1
// Part 3: $
#define MY_FUNC$%0,%1$
// Part 1: Module
// Part 2: ->%0(%1
// Part 3: )
#define Module->%0(%1)
// Part 1: CRAZY
// Part 2: ^S%0Y%1
// Part 3: Z
#define CRAZY^S%0Y%1Z
// Part 1: RF
// Part 2: :%0:%1[%2](%3)<%4
// Part 3: >
#define RF:%0:%1[%2](%3)<%4>
Przykłady powyżej powinny wyjaśnić Ci wszystko na temat wzorców podmiany , w dalszych częściach poradnika będzie można jednak zauważyć bardziej szczegółowe opisy. Bardzo częstym błędnym założeniem jest że makro musi składać się z takiej konstrukcji „NAZWA(parametr,parametr)” – tak nie jest w PAWNie ( i szczerze mówiąc jest to niesamowita cecha ) ! Podmiana Są 4 rodzaje podmian ( według mojej klasyfikacji ) – definicje, makra funkcyjne, makra z kodem i specjalne. Definicje takie jak „MAX_PLAYERS” definiują pojedynczą liczbę lub ciąg znaków i sa używane zamiast tych stałych. Makra funkcyjne są używane w miejscach w których mogły by być użyte funkcje a nawet sa podobne do funkcji ( często będę używał nazewnictwa funkcji zamiast konwencji nazewnictwa makr ). Makra z kodem posiadają w sobie kod często wielolinijkowy – często są nazwy spełniają konwencje nazewnictwa makr aby pokazać ze w tym miejscu dzieje się coś specjalnego , makra tego typu wyglądają często jak makra funkcyjne ale nie mogą być zawsze używane zamiennie. Makra specjalne sa specjalnymi konstrukcjami rozszerzającymi możliwości języka np. Pętla foreach dla pawn która wygląda jak zwykła instrukcja jezyka pawn ale tak naprawdę jest makrem używającym pętli for. Makra specjalne będą opisane później a definicje zostały już opisane – w tym poradniku zajmiemy sie makrami funkcyjnymi i z kodem. Makra Funkcyjne Część 1 w pewien sposób opisywała już makra i parametry ale jest oczywiście tego więcej.Makra funkcyjne mogą być używane zamiast funkcji ( lub odwrotnie ). Wyobraźmy sobie ze mamy taki kod:
enum E_DATA
{
Float:E_DATA_HEALTH,
E_DATA_MONEY
}

new gPlayerData[MAX_PLAYERS][E_DATA];

public OnPlayerConnect(playerid)
{
gPlayerData[playerid][E_DATA_MONEY] = 100;
return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
printf("player %d had %d", playerid, gPlayerData[playerid][E_DATA_MONEY]);
return 1;
}
Używanie tych wszystkich tablic może być w pewien sposób nieporęczne wiec postaramy się wprowadzić do tego wszystkiego makra:
enum E_DATA
{
Float:E_DATA_HEALTH,
E_DATA_MONEY
}

new gPlayerData[MAX_PLAYERS][E_DATA];

GetMoney(playerid)
{
return gPlayerData[playerid][E_DATA_MONEY];
}

SetMoney(playerid, money)
{
gPlayerData[playerid][E_DATA_MONEY] = money;
}

public OnPlayerConnect(playerid)
{
SetMoney(playerid, 100);
return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
printf("player %d had %d", playerid, GetMoney(playerid));
return 1;
}
Możemy nawet jeszcze bardziej skrócić ten kod dzięki makrom:
enum E_DATA
{
Float:E_DATA_HEALTH,
E_DATA_MONEY
}

new gPlayerData[MAX_PLAYERS][E_DATA];

#define GetMoney(%0)                                                            \
gPlayerData[(%0)][E_DATA_MONEY]

#define SetMoney(%0,%1)                                                         \
gPlayerData[(%0)][E_DATA_MONEY] = (%1)

public OnPlayerConnect(playerid)
{
SetMoney(playerid, 100);
return 1;
}

public OnPlayerDisconnect(playerid, reason)
{
printf("player %d had %d", playerid, GetMoney(playerid));
return 1;
}
Ta wersja kodu jest tak samo szybka jak pierwsza wersja kodu i tak prosta jak druga. Warto zauważyć że makra nie zawierają operacji matematycznych ale i tak są owinięte w nawiasy. Spróbuj pomyśleć chwile co by się stało bez użycia nawiasów tzn „%0” bez nawiasów. W większości nie popełniają takie błędu ale wszystko się może zdarzyć:
#define GetMoney(%0)                                                            \
gPlayerData[%0][E_DATA_MONEY]

public OnPlayerDisconnect(playerid, reason)
{
printf("player %d had %d", playerid, GetMoney( 0], moo[10 ));
return 1;
}
Makra funkcyjne tutaj są zaprojektowane aby zastąpić funkcje i być używane jak funkcje – wiec dlatego używają konwencji nazewnictwa funkcji a nie makr. Jednym z wad używania makr funkcyjnych jest brak nazw parametrów więc aby dowiedzieć się co oznaczają parametry należy przeanalizować kod. Kolejny problemem może być używanie takiego kodu:
SetMoney(playerid, money++);
Tego typu problemy są wyjaśnione szczegółowo w części 1. W tym przykładzie kontrola błędów nie jest aż tak ważna skoro parametr jest używany tylko raz , więc parametr jest inkrementowany tylko raz. Jeśli piszesz makro które ma zastąpić małą funkcję i każdy parametr może być użyty tylko raz można używać konwencji nazewnica funkcji jednak jeśli parametr może być używany częściej niż raz warto nazwać makro zgodnie z konwencją definicji aby pozostali programiści wiedzieli aby nie używać tego typu problematycznych konstrukcji:
SET_MONEY(playerid, money);
money++;
Kilka przykładów makr funkcyjnych:
// Wrapper around printf.
#define DEBUG(%0)                       printf("Debug: %s", (%0))

// Square two numbers - watch out for increments.
#define SQUARE(%0)                      ((%0) * (%0))

// Set a timer to do a passed function every second.
#define EVERY_SECOND(%0)                SetTimer((%0), 1000, 1)

// Show an icon - calls "GetNextIcon", an imaginary custom function (or macro).
#define ShowIcon(%0,%1,%2,%3)                                                   \
SetPlayerMapIcon(playerid, GetNextIcon(playerid), %1, %2, %3, %0, COLOR_RED)
Makra z Kodem Makro z kodem są podobne do makr funkcyjnych ale nie mogą być używane w tych samych miejscach: Makra funkcyjne
// Declaration
#define MY_FUNC(%0)                     ((%0) + 7)

// Uses
if (MY_FUNC(var))

var = MY_FUNC(var);

OtherFunc(MY_FUNC(42));
Makra z Kodem
// Declaration
#define MY_FUNC(%0)                     new b = ((%0) + 7)

// All invalid
if (MY_FUNC(var))

var = MY_FUNC(var);

OtherFunc(MY_FUNC(42));
Makra funkcyjne są poprawne we wszystkich użytych miejscach ponieważ zawiera tylko jedno wyrażenie , makro z kodem zawiera jednak konstrukcje przypisania do zmiennej „if (new b = ((var + y))” jest oczywiście niepoprawne ( to samo „var = new b = ((var) + 7);” ) PS. „a = b = c;” jest poprawną konstrukcją. Pomijanie Nawiasów Część 1 mówiła o owijaniu parametrów makr w nawiasy , jednak nie zawsze jest tak czarno biało ! Przykład z wysyłaniem tekstu do gracza. Który jest kompletnie bez sensu ze względu na możliwość używania formatowania w client_print ale powinien wyjaśnić wszystko:
client_print( id ,  print_chat , "Hello there");
Teraz wyobraźmy sobie że chcemy dodać ID gracza do wiadomości:
new str[32];
format(str, sizeof (str), "Hello there %d", id);
client_print( id ,  print_chat , str);
Jest oczywiście sposób na zrobienie tego z użyciem makr:
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
new str[32];                                                                \
format(str, sizeof (str), (%2), %3);                                        \
client_print((%0), (%1), str)
Zauważ że „%3” nie jest owinięte w nawiasy i nie dodaliśmy średnika na końcu. Pomyśl co się stanie gdy użyjemy tego makra:
SendClientMessageFormatted(id, print_chat , "Hello there %d", id);
Wygeneruje:
new str[32];
format(str, sizeof (str), ("Hello there %d"), id);
client_print((id), (print_chat), str);
A
SendClientMessageFormatted(id, print_chat, "Hello there %d, you have $%d", id, money);
Wygneruje:
new str[32];
format(str, sizeof (str), ("Hello there %d, you have $%d"), id, money);
client_print((id), (print_chat), str);
A teraz wyobraź sobie co by się stało jeśli owinęli byśmy „%3” w nawiasy i dodali byśmy średnik na końcu.
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
new str[32];                                                                \
format(str, sizeof (str), (%2), (%3));                                      \
client_print((%0), (%1), str);
SendClientMessageFormatted(id, print_chat, "Hello there %d", id);
Wygeneruje:
new str[32];
format(str, sizeof (str), ("Hello there %d"), (playerid));
client_print((id), (print_chat), str);;
„id” jest owinięte w nawiasy ( co jest poprawne ) dodatkowo mamy dwa średniki końcu linii. Jeden z makra a drugi z normalnego kodu , średnik nie jest częścią wzorca wyszukiwania więc nie jest podmieniany. Spróbuj wymyśleć co się stanie przy użyciu takiego kodu:
#define a() ;
a();
Wracając do poprzednie przykładu
SendClientMessageFormatted(id, print_chat , "Hello there %d, you have $%d", id, money);
Wygeneruje:
new str[32];
format(str, sizeof (str), ("Hello there %d"), (id, money));
client_print((id), (print_chat), str);;
„id, money” zawartość „%3” została owinięta w nawiasy – jest to oczywiście błędna konstrukcja. I nadal mamy podwójny średnik ! Zakres Makr z kodem Kod powyżej ma jeden problem jeśli zrobimy coś takiego:
SendClientMessageFormatted(id, print_chat
, "Hello there %d", id);
SendClientMessageFormatted(id, print_chat, "You have $%d", money);
Wygenerujemy taki kod:
new str[32];
format(str, sizeof (str), ("Hello there %d"), id);
SendClientMessage((id), (print_chat), str);
new str[32];
format(str, sizeof (str), ("You have $%d"), money);
SendClientMessage((id), (print_chat), str);
Kompilacja kodu wyżej nie powiedzie się ponieważ deklarujemy str dwukrotnie. Aby tego uniknąć należy zawęzić zakres żywotności zmiennych utworzonych w makrze tylko do makra. Najprostszy sposób to:
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
{                                                                           \
new str[32];                                                            \
format(str, sizeof (str), (%2), %3);                                    \
client_print((%0), (%1), str);                                     \
}
Co Wygeneruje:
{
new str[32];
format(str, sizeof (str), ("Hello there %d"), id);
client_print((id), (print_chat), str);
};
{
new str[32];
format(str, sizeof (str), ("You have $%d"), money);
client_print((id), (print_chat), str);
};
Ten sposób rozwiązuje problem zasięgu zmiennych ale tworzą kolejny tzn. Nawiasy posiadają średnik za sobą odziedziczony po wywoływaniu makra. To ten sam problem podwójnych średników który widzieliśmy wyżej. Potrzebujemy jakiegoś sposobu oznaczania zakresu zmiennych i konstrukcji wymagającej średnika na końcu. Jedyna konstrukcja która spełnia te wymagania jest „do-while” ale nie chcemy zapętlać kodu!
#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
do                                                                          \
{                                                                           \
new str[32];                                                            \
format(str, sizeof (str), (%2), %3);                                    \
client_print((%0), (%1), str);                                     \
}                                                                           \
while (false)
Prawie jesteśmy u celu ! Teraz jednak kompilator zgłasza uwagi co do warunku false. False nigdy nie jest prawdziwe wiec kompilator myśli ze kod jest niepotrzebny. Ostateczny kod to:
#if !defined FALSE
// stock variables are not included if they're not used.
stock
bool:FALSE = false;
#endif

#define SendClientMessageFormatted(%0,%1,%2,%3)                                 \
do                                                                          \
{                                                                           \
new str[32];                                                            \
format(str, sizeof (str), (%2), %3);                                    \
client_print((%0), (%1), str);                                     \
}                                                                           \
while (FALSE)
Pozbylismy sie problemu zasiegu zmiennych i srednika. Np. Kilka przykładów makr z kodem:
// Wrapper around printf with _DEBUG check.
#define DEBUG(%0)                       if (_DEBUG == 1) printf("Debug: %s", (%0))

// Declare a variable.
#define VAR(%0)                         new %0 = -1

// Shortcut to declare a public function.
#define PUBLIC(%0,%1)                                                           \
forward %0(%1);                                                             \
public %0(%1)
Dlugie makra Linie w PAWN mogą być maksymalnie długie na 512 znaków w tym znak nowej linii wiec maksymalnie 510 znaków. Limit ten jest sprawdzany już po wykonaniu w całości preprocesora. Z tego powodu finalna wersja „SendClientMessageFormatted” nie skompiluje się. Kod powyżej jest tylko pokazaniem pewnej koncepcji nie jest wpelni poprawnym kodem , potrzeba kilku zmian aby zaczął działać. Aby zredukować długość linii warto usunąć niepotrzebne spacje tabulatory itp. Więcej o tym w następnych częściach.

Dodaj komentarz

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.