O pré-processador
A primeira etapa da compilação de um arquivo Pawn para P-code é o “pré-processamento”: um filtro genérico que ajusta o texto antes de enviá-lo ao parser. Nesse estágio, comentários são removidos, blocos condicionais são avaliados, diretivas são executadas e ocorrem substituições de texto. As diretivas são resumidas na página 117; aqui focamos nas substituições.
O pré-processador age sobre cada linha imediatamente após ser lida. Não há checagem sintática nessa fase. Embora ofereça recursos poderosos, o uso descuidado pode introduzir erros difíceis de rastrear.
Farei menções a C/C++ porque o pré-processador Pawn se inspira nele —embora não seja compatível.
A diretiva #define cria macros. Alguns exemplos:
#define maxsprites 25
#define CopyRightString "(c) Copyright 2004 by me"
No script, eles podem ser usados como constantes. Exemplo:
#define maxsprites 25
#define CopyRightString "(c) Copyright 2004 by me"
main()
{
print( Copyright )
new sprites[maxsprites]
}
Para casos simples, é melhor usar as construções equivalentes da linguagem:
const maxsprites = 25
stock const CopyRightString[] = "(c) Copyright 2004 by me"
Essas declarações oferecem melhor checagem e permitem tags. No caso de strings, declare o array como const e stock: const impede alterações, e stock retira o símbolo se nunca for usado.
Macros com substituição podem ter até 10 parâmetros. Um uso comum é simular funções pequenas:
Listing: the “min” macro
#define min(%1,%2) ((%1) < (%2) ? (%1) : (%2))
Se você veio de C/C++, reconhecerá o hábito de cercar argumentos e a expressão inteira com parênteses.
Se a macro for usada assim:
Listing: bad usage of the “min” macro
new a = 1, b = 4
new min = min(++a,b)
the preprocessor translates it to:
new a = 1, b = 4
new min = ((++a) < (b) ? (++a) : (b))
Isso pode incrementar a duas vezes —uma armadilha clássica. Por isso, é comum adotar uma convenção (como usar maiúsculas) para distinguir macros de funções.
Para entender a importância dos parênteses, veja:
#define ceil_div(%1,%2) (%1 + %2 - 1) / %2
Essa macro divide arredondando “para cima”. Usando-a assim:
new a = 5
new b = ceil_div(8, a - 2)
o pré-processador gera new b = (8 + a - 2 - 1) / a - 2. Devido à precedência dos operadores, b vira 0 (se a = 5). O esperado era ceil(8 / (a - 2)). Envolver os parâmetros (e, de preferência, toda a expressão) em parênteses resolve:
#define ceil_div(%1,%2) ( ((%1) + (%2) - 1) / (%2) )
O matching de padrões é mais sutil do que detectar trechos parecidos com chamadas. O padrão é literal, mas aceita qualquer texto nos lugares marcados como parâmetros. Por exemplo:
Listing: macro that translates a syntax for array access to a function call
#define Object[%1] CallObject(%1)
Se a expansão de uma macro contém trechos que coincidem com outras macros, essas substituições ocorrem durante a invocação, não na definição:
#define a(%1) (1+b(%1))
#define b(%1) (2\*(%1))
new c = a(8)
will evaluate to “new c = (1+(2*(8)))”, even though the macro “b” was not defined at the time of the definition of “a”.
As regras para matching são:
- Não há espaços no padrão. Se precisar, use a sequência
\32;. Já o texto resultante pode ter espaços. - Sequências de escape são aceitas (úteis, por exemplo, para casar com
%literal). - O padrão não pode terminar com parâmetro (isto é, “set:%1=%2” é inválido). Para casar com fim de instrução, adicione
;. - O padrão deve começar com letra,
_ou@. O trecho inicial alfanumérico é o “nome” ou prefixo da macro —é o que você usa emdefined/#undef. - Ao casar padrões, o pré-processador ignora espaços entre símbolos alfanuméricos ↔ não alfanuméricos, exceto entre símbolos idênticos. Ex.:
abc(+-)casa comabc ( + - ), enquantoabc(--)não casa comabc(- -). - Existem até 10 parâmetros (
%0–%9), em qualquer ordem. - Assim como outras diretivas, o
#definedeve caber em uma linha. Se necessário, finalize a linha com\. O texto de entrada também precisa caber em uma linha.
Lembre-se de que macros (especialmente as parametrizadas) podem mudar completamente o significado de uma linha —o que parece acesso a array pode virar chamada de função, por exemplo.
Hosts que embutem o parser podem oferecer opções para visualizar o resultado dessas substituições. No conjunto de ferramentas padrão, consulte o “Pawn booklet — Implementor’s Guide” para instruções de uso do compilador e runtime.
Operator precedence: 110
Directives: 117