Meu amigo Alexssander me pediu para escrever sobre macros e logo em seguida eu vi um post muito abrangente que trata justamente de macros e resolvi traduzi-lo, vamos lá.
O X++ prove macros de uma maneira fácil e expansiva. Com eles, você pode definir e usar valores para fazer compilações condicionais e etc… Neste artigo eu vou descrever a semantica da construção e prover modos para resolver alguns dos problemas que iniciantes e experts tem com os macros.
Macros não são estruturados tanto que eles não seguem a estrutura do X++. O tratamento dos macros acontece antes que o texto chegue ao compilador.
Macros podem ser usados em métodos, declarações de classes, jobs e em todos os outros lugares onde se há código X++.
A semântica de cada chave macro é descrita abaixo:
Construtores
#define
A sintaxe é: #define.MyName(SomeValue)
Isto define um macro chamado MyName com o valor SomeValue. Quando esta definição está em uso, qualquer referencia à #MyName será substituída pelo que há em SomeValue. A definição semântica não tem outro objetivo além de definir o símbolo MyName: O texto não atinge o próprio compilador. Quando a compilação do método atual acabar, o símbolo (MyName neste caso) não é mais lembrado. Se o símbolo já estava definido, o valor antigo é descartado e substituído pelo novo valor.
#globaldefine
A sintaxe é: #globaldefine.MyName(SomeValue)
Este tem a mesma semântica do define descrito acima.
#definc
A sintaxe é: #definc.MyName
Este construtor é usado só quando o valor é um inteiro. O pré processador incrementará o valor do símbolo por um. Se o valor não foi definido antes da instrução #definc é gerado um erro. Caso o valor não é um inteiro, o valor antigo será sobrescrito pelo valor 0 e então incrementado, tornado-se 1.
#defdec
A sintaxe é: #defdec.MyName
Este é o mesmo caso do anterior, no entanto o pré processador irá decrementar o valor do símbolo por um. Caso não exista, gerará um erro e caso exista e não seja um inteiro, será convertido para 0 e então decrementado por 1, o que retorna -1.
#undef
A sintaxe é: #undef.MyName
Como o nome já diz, ele remove a definição do símbolo e caso o símbolo não tenha sido previamente declarado, nada acontece.
#if … #endif
A sintaxe é:
#if.MySymbol
…
#endif
Ou
#if.MySymbol(SomeValue)
…
#endif
No primeiro caso o texto marcado com … nos exemplos acima é inserido dentro do fonte se o MySymbol foi préviamente definido, já no segundo caso, o conteúdo é inserido no fonte apenas se o valor definido for igual ao que está sendo validado.
O construtor #if pode ser usado em qualquer nível, mas não há um construtor #else.
#ifnot … #endif
A sintaxe é: #ifnot.MySymbol
…
#endif
Ou
#ifnot.MySymbol(SomeValue)
…
#endif
É exatamente igual ao anterior, no entanto, negativo. Equivale ao != usado em ifs.
#macrolib
A sintaxe é: #macrolib.MyName
Neste caso, o nome deve referenciar um macro existente na AOT. O texto do Macro é então processado pelo pré processador. O valor da macro é inserido no código fonte onde a diretiva aparece. Caso o Macro não exista na AOT um erro será gerado.
#macro / #localmacro
As chaves #macro e #localmacro são intercambiáveis, não há diferença na semântica das duas. O construtor é usado para definir um símbolo para detonar um conteúdo que possivelmente usará várias linhas.
A sintaxe é:
#localmacro.MySymbol
….
#endmacro
Exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class MyBaseClass extends Runbase { int v1; #define.myMacro(“Hello world”) #localmacro.currentlist v1 #endmacro public container pack() { return [#currentlist]; // #currentlist expande-se para v1 } public void run() { print #myMacro; // #myMacro expande-se para “Hello world” } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class MyDerivedClass extends myBaseClass { int v2; #localmacro.currentlist v2 #endmacro public container pack() { return [super(), #currentlist]; // #currentlist expande-se para v2 } public void run() { print #myMacro; // #myMacro expande-se para “Hello world” } } |
#MySymbol
A sintaxe é: #MySymbol
Insere o valor do símbolo no código e é um erro se referir a um símbolo que não foi definido. Se o nome denotar um Macro definido na AOT, o texto do nó será processado pelo pré processador (neste caso, #MySymbol é um atalho para #macrolib.MySymbol).
Problemas comuns
Nesta parte do post, eu gostaria de descrever alguns dos problemas que os desenvolvedores tem quando utilizam macros.
Macros e parâmetros
Isto parece ser um fato pouco conhecido e a origem de certa confusão: Os valores dos símbolos podem receber os placeholders %
#MyString(X++)
o resultado será:
“Hello World from X++”
Veja que no exemplo acima não usamos aspas para passar o valor X++ como argumento. Caso usado, irá gerar um erro de compilação!
A confusão também pode acontecer por causa da notação % também é usada como parâmetro de substituição na função strFmt. E como sabem, esta função tem uma lista de parâmetros com comprimento variável, e cada referencia de %n no primeiro parâmetro (a string) será expandida para conter a representação informada no argumento n, conforme podemos ver abaixo:
print strfmt(“The value is %1”, theValue);
Agora, alguns programadores tentarão fazer isto:
#define.TheText(“The value is %1”)
print strfmt(#TheText, theValue);
Mas a substituição em macros acontece antes do compilador passar pelo código. A substituição do macro não irá encontrar o parâmetro para ser colocado onde o %1 encontra-se, então o compilador encontrará:
print strfmt(“The value is “, theValue);
o que provavelmente não é a intenção do desenvolvedor.
Macros nas declarações de classes
Algumas confusões surgem em situações que os macros são definidos na declaração das classes. Para entender como isto é tratado é necessário rever o que o compilador faz quando ele compila um método. O processo inicia por calcular a sequencia das derivações da classe que a classe faz parte. Então o processo analisa cada declaração da classe com a primeira derivação primeiro, preenchendo sua tabela interna de símbolos. Após compilar a declaração da classe o compilador compila os métodos. Qualquer símbolo definido em qualquer classe derivada será subseqüentemente disponível para uso nos métodos. Símbolos definidos na declaração da classes talvez sejam substituídos por valores definidos em mais de uma derivação da classe.
Parênteses dentro das strings nos macros
O scanner que trata com as strings nos macros é muito simplista. Ele não ira tratar a situação onde onde parênteses forem incluídosa string, ou seja
#define.Another(“(This is text in parenthesis)”)
irá gerar um erro léxico. Se você precisar fazer isto, você deve usar no lugar a diretiva #localmacro:
#localmacro.Another
“(This is text in parenthesis)”
#endmacro
Neste caso, o fim do macro é sinalizado pela string #endmacro e não no parêntese direito.
Aqui acaba a tradução.
Se navegarem pela AOT vocês vão ver exemplos de macros mais compostos e complexos, que é o exemplo do macro InventDimSelect que implementa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | select #ifnot.empty(%4) %4 #endif %1 #ifnot.empty(%5) index hint %5 #endif #ifnot.empty(%6) order by %6 #endif where (%1.ConfigId == %2.ConfigId || ! %3.ConfigIdFlag) && (%1.InventSizeId == %2.InventSizeId || ! %3.InventSizeIdFlag) && (%1.InventColorId == %2.InventColorId || ! %3.InventColorIdFlag) && (%1.InventSiteId == %2.InventSiteId || ! %3.InventSiteIdFlag) && (%1.InventLocationId == %2.InventLocationId || ! %3.InventLocationIdFlag) && (%1.InventBatchId == %2.InventBatchId || ! %3.InventBatchIdFlag) && (%1.WMSLocationId == %2.WMSLocationId || ! %3.WMSLocationIdFlag) && (%1.WMSPalletId == %2.WMSPalletId || ! %3.WMSPalletIdFlag) && (%1.InventSerialId == %2.InventSerialId || ! %3.InventSerialIdFlag) #InventDimDevelop |
e é utilizado em vários lugares para se fazer algumas consultas as dimensões de estoque. Veja que ele válida se alguns parâmetros foram passados e com isso consegue fazer if no meio de instruções SQL, o que é muito útil já que em uma instrução ‘normal’, você não conseguiria.
Existem outras possibilidades, alias, as possibilidades nos usos de macros são infinitas, vai da criatividade de cada um! Eu tenho outro amigo (Daniel Zanni) que usa os macros para verificar se determinada custom está ativa nas configuration keys e caso sim, aplica a custom no que diz respeito a código.
É isso ai pessoal, espero que tenha ajudado.
Abraço,
Pichler
3 Responses
Alexssander
06|Oct|2010 1Pichler, muito bom o post, obrigado!
Porém estou com um probema.
Criei uma macro na AOT e passei a seguinte str:
#define.MyJournal(‘C. PAGAR’)
C. PAGAR é um registro na table LedgerJournalName do campo LedegerName, porém quero usar a minha macro no find da LedgerJournalName e ele fala que a macro não existe…
Criei um job com o seguinte código:
info(LedgerJournalName::find(#MyJournal).Name);
E nesse caso ele ignora a macro criada na AOT falando que não existe a macro.
Porém se definir a macro no job:
#define.MyJournal(‘C. PAGAR’);
info(LedgerJournalName::find(#MyJournal).Name);
Funciona certinho e me retorna o Name.
Ricardo Pichler
06|Oct|2010 2Alex, o problema no seu caso é nomenclatura!
Defina o macro na AOT com o nome MyJournal, dentro dele, declare: #define.MyJournals(“Valor”)
No código, na parte onde declara as variáveis, coloque a entrada: #MyJournal (sem ponto e virgula no final da linha)
No find, use info(LedgerJournalName::find(#MyJournals).Name);
Beleza?
Abraço!
Alexssander
06|Oct|2010 3Valeu Pichler, comi bola mesmo.
Abraço!
Leave a reply