UMA EXTENSÃO ORIENTADA A ASPECTOS PARA C#tcc.ecomp.poli.br/20102/monografia-daa.pdf · Entretanto...
-
Upload
trannguyet -
Category
Documents
-
view
218 -
download
0
Transcript of UMA EXTENSÃO ORIENTADA A ASPECTOS PARA C#tcc.ecomp.poli.br/20102/monografia-daa.pdf · Entretanto...
UMA EXTENSÃO ORIENTADA A
ASPECTOS PARA C#
Trabalho de Conclusão de Curso
Engenharia da Computação
Autor: Diego Albuquerque de Araújo
Orientador: Prof. Dr. Luis Carlos de Sousa Menezes
ESCOLA
POLITÉCNICA
DE PERNAMBUCO
Diego Albuquerque de Araújo
UMA EXTENSÃO ORIENTADA A
ASPECTOS PARA C#
Monografia apresentada como requisito parcial para obtenção do diploma de Bacharel em Engenharia da Computação pela Escola Politécnica de Pernambuco - Universidade de Pernambuco.
Recife, Dezembro de 2010.
ii
Agradecimentos
Agradeço primeiramente a minha família por ter sempre me fornecido todo o suporte necessário. Isso é uma das coisas mais importantes na minha vida.
A minha mãe, Elenice Araújo, por ser a maior responsável pela minha
formação e que sempre me apoiou nas decisões que tomei na vida, auxiliando-me em tudo o quanto foi preciso. Sem seu esforço nada teria acontecido.
A meu pai, José Hamilton, por ser um pai maravilhoso que sempre me acolheu com seu carinho e companheirismo.
A meu irmão, Leandro Araújo, que sempre me incentivou e me deu forças para seguir em frente.
Aos meus amigos da faculdade que lutaram junto a mim durante os cinco
anos da graduação, meus sinceros agradecimentos.
Agradeço aos professores da graduação, em especial ao meu orientador Luis Carlos, por essa e outras orientações e por todo conhecimento e ensinamentos transmitidos ao longo dessa jornada.
Agradeço também a todas as pessoas que contribuíram, direta ou indiretamente, para essa realização.
E a Gabriela, minha noiva, pelo seu amor e carinho, e por diversas vezes
compreender a minha ausência durante a caminhada na conquista deste sonho.
iii
Resumo
A programação orientada a aspectos (POA) propõe solucionar alguns
problemas encontrados na programação orientada a objetos. Entre estes problemas
podemos citar, em particular, a separação de interesses transversais (cross cutting
concerns). Entretanto a programação orientada a aspectos ainda não conseguiu o
nível de popularização necessário para a utilização, em larga escala, da técnica, se
comparado com o nível de popularização da programação orientada a objetos.
Apesar dos esforços para a popularização da POA em projetos comerciais, quando
nos deparamos com a plataforma de desenvolvimento da Microsoft, o .NET
Framework, nos surpreendemos com a escassez de ferramentas, que implementem
por completo os conceitos da programação orientada a aspectos, e de IDE’s que
facilitem o uso das técnicas incorporadas por tal paradigma. Com o intuito de
popularizar a POA em projetos que envolvam a tecnologia Microsoft .NET
Framework, este trabalho tem como principal objetivo desenvolver uma extensão
orientada a aspectos (linguagem e compilador) da linguagem de programação C#
que possa ser utilizada em projetos comerciais de diferentes portes.
iv
Abstract
The aspect-oriented programming aims to solve some problems that are found
in object-oriented programming. Among these problems we give as an example the
separation of crosscutting concerns. However the aspect-oriented programming has
not yet achieved the desired maturity to be adopted in large-scale projects as the
object-oriented programming. Despite the efforts to popularize the AOP on
commercial projects unexpectedly the Microsoft .NET Framework does not have a
tool that fully implements the concepts of aspect-oriented programming and also an
IDE that makes easier the use of the techniques supported by this paradigm. In order
to popularize the AOP in .NET projects, this work has as main objective the
development of an aspect-oriented extension (language and compiler) of the
language C# that can be used on different scale commercial projects.
Sumário LISTA DE FIGURAS ................................................................................................................................. 1
LISTA DE ABREVIATURAS E ACRÔNIMOS............................................................................................... 2
CAPÍTULO 1 INTRODUÇÃO .................................................................................................................... 4
1.1 CARACTERIZAÇÃO DO PROBLEMA ..................................................................................................... 4
1.2 OBJETIVOS .................................................................................................................................. 5
1.2.1 Objetivo Geral................................................................................................................. 5
1.2.2 Objetivos Específicos ....................................................................................................... 5
1.3 RESULTADOS ESPERADOS ............................................................................................................... 6
1.4 ORGANIZAÇÃO DO DOCUMENTO ...................................................................................................... 6
CAPÍTULO 2 FUNDAMENTAÇÃO TEÓRICA ............................................................................................ 8
2.1 PROGRAMAÇÃO ORIENTADA A ASPECTOS......................................................................................... 12
2.2 ORIENTAÇÃO A ASPECTOS NO FRAMEWORK .NET............................................................................... 14
2.3 PROJETO DE LINGUAGENS DE PROGRAMAÇÃO .................................................................................... 16
2.3.1 Compiladores................................................................................................................ 16
2.3.2 Gramáticas livres de contexto ....................................................................................... 19
2.3.3 Geradores de analisadores sintáticos ............................................................................ 20
2.3.4 Backus-Naur Form (BNF) e Extended Backus-Naur Form (EBNF) ................................... 21
CAPÍTULO 3 A LINGUAGEM D# ............................................................................................................ 23
3.1 O ASPECTO ............................................................................................................................... 23
3.2 PONTOS DE JUNÇÃO .................................................................................................................... 27
3.3 PONTOS DE CORTE ...................................................................................................................... 29
3.4 ADENDOS ................................................................................................................................. 37
3.5 DECLARAÇÕES INTER-TIPOS .......................................................................................................... 41
3.5.1 Declarações de membros .............................................................................................. 41
3.5.2 Declarações de Hierarquia ............................................................................................ 42
3.5.3 Declarações de Precedência .......................................................................................... 43
3.5.4 Declarações de Erros e Avisos ....................................................................................... 43
CAPÍTULO 4 O PROJETO DO COMPILADOR ......................................................................................... 45
4.1 ANÁLISE LÉXICA E SINTÁTICA ......................................................................................................... 45
4.2 ANÁLISE SEMÂNTICA ................................................................................................................... 49
4.3 GERAÇÃO DE CÓDIGO .................................................................................................................. 51
4.4 COMBINAÇÃO ............................................................................................................................ 51
4.5 PROVA DE CONCEITO ................................................................................................................... 53
4.5.1 Estrutura do protótipo do compilador ........................................................................... 53
4.5.2 Visão geral do processo de compilação ......................................................................... 55
4.5.3 Um exemplo de aplicação ............................................................................................. 57
CAPÍTULO 5 CONCLUSÃO E TRABALHOS FUTUROS ............................................................................. 61
5.1 DISCUSSÃO DOS RESULTADOS E CONTRIBUIÇÕES ................................................................................. 61
5.2 TRABALHOS FUTUROS .................................................................................................................. 62
BIBLIOGRAFIA...................................................................................................................................... 65
APÊNDICE A GRAMÁTICA DA LINGUAGEM D# .................................................................................... 68
1
Lista de Figuras
Figura 1. Mapeamento do interesse transversal de Logging utilizando POO.
[Fonte: adaptado por Soares [2])................................................................................ 9
Figura 2. Exemplo de código entrelaçado. [Fonte: [24]] ......................................... 10
Figura 3. Implementação de registro de ações no servidor Apache Tomcat.
[Fonte: adaptado por Soares [25]] ............................................................................ 10
Figura 4. Implementação de CCC nos paradigmas OO e AO. [Fonte: [2]] ............. 11
Figura 5. Etapas de desenvolvimento da POA. [Fonte: [2]] ................................... 13
Figura 6. Fases de Compilação. [Fonte: [9]] .......................................................... 18
Figura 7. Hierarquia de Chomsky. [Fonte: Wikipédia] ............................................ 19
Figura 8. Diagrama de sequência ilustrando os pontos de junção na execução de
um programa. [Fonte: [4]] ......................................................................................... 28
Figura 9. Diagrama de sequência ilustrando a invocação implícita de
adendos.[Fonte: [4]] ................................................................................................. 39
Figura 10. A ferramenta ANTLRWorks [Fonte: próprio autor] .............................. 48
Figura 11. Estrutura da implementação do padrão Visitor. [Fonte: próprio autor] . 50
Figura 12. Classes do protótipo do compilador da linguagem D#. [Fonte:próprio
autor] 53
Figura 13. Classe Weaver. [Fonte: próprio autor]................................................. 54
Figura 14. Visão Geral do Processo de Compilação. [Fonte: adaptado de [32]] .. 55
Figura 15. Visualização do código combinado na ferramenta .NET Reflector.
[Fonte: próprio autor] ................................................................................................ 60
2
Lista de Abreviaturas e Acrônimos
Em ordem alfabética
ANTLR – ANother Tool for Language Recognition (Outra ferramenta para
reconhecimento de linguagens)
AST – Abstract Syntax Tree (Árvore Sintática Abstrata)
BNF – Backus-Naur Form (Forma de Backus-Naur)
CCC – Crosscutting Concerns (Interesses Transversais)
CIL – Common Intermediate Language (Linguagem Intermediária Comum)
CLR – Common Language Runtime (Ambiente de Execução Comum para
Linguagens)
EBNF – Extended Backus-Naur Form (Forma de Backus-Naur Estendida)
ECMA – European Computer Manufacturers Association (Associação
Européia de manufaturas para Computação)
IDE – Integrated Development Enviroment (Ambiente de Desenvolvimento
Integrado)
JEE – Java Enterprise Edition
LL – Left to right with Leftmost derivation (Esquerda para direita com
derivação mais à esquerda)
LR – Left to right with Rightmost derivation (Esquerda para direita com
derivação mais à direita)
OA – Orientado a Aspectos
OO – Orientado a Objetos
PC – Ponto de Corte
PG – Parser Generator (Gerador de Analisadores sintáticos)
POA – Programação Orientada a Aspectos
4
Capítulo 1
Introdução
Atualmente, a grande demanda por softwares cada vez mais complexos,
exige a criação de novos métodos, técnicas e ferramentas que auxiliem no processo
de desenvolvimento de software. Neste trabalho propomos a utilização da
programação orientada a aspectos no desenvolvimento de sistemas na plataforma
.NET através do desenvolvimento de uma extensão orientada a aspectos da
linguagem de programação C#.
1.1 Caracterização do Problema
A programação orientada a aspectos (POA) [1] propõe solucionar alguns
problemas existentes na programação orientada a objetos. Entre estes problemas
podemos citar, em particular, a separação de interesses transversais (cross cutting
concerns – CCC) [3]. Entretanto, a programação orientada a aspectos ainda não
conseguiu o nível de popularização necessário para a utilização em larga escala da
técnica, se comparado com o nível de popularização da programação orientada a
objetos [2,4,5]. Entre as possíveis causas da não popularização da técnica,
podemos citar a falta de ferramentas e linguagens que auxiliem no desenvolvimento
de projetos que utilizam os conceitos da POA. Então, entende-se que para o avanço
da POA faz-se necessário a criação de linguagens de programação e Ambientes de
Desenvolvimento Integrado (IDE‟s) que auxiliem no desenvolvimento de projetos
comerciais de diferentes portes.
Partindo do pressuposto que a criação de ferramentas é de extrema
importância, foram desenvolvidas diversas implementações de linguagens e
ferramentas [5] que oferecem suporte à POA: entre elas podemos citar a linguagem
AspectJ [4], que é uma extensão da linguagem JAVA para a POA. Entretanto,
quando lidamos com a plataforma de desenvolvimento da Microsoft, o .NET
Framework [6], nos surpreendemos com a escassez de ferramentas [8], que
5
implementem por completo os conceitos da programação orientada a aspectos, e de
uma IDE que facilite o uso das técnicas incorporadas por tal paradigma. A falta de
tais ferramentas acaba atuando como um empecilho para a popularização da POA
no mundo .NET, que acaba refletindo em uma grande desvantagem do framework
em relação a outras soluções de mercado como, por exemplo, JAVA.
1.2 Objetivos
1.2.1 Objetivo Geral
Com o intuito de popularizar a POA em projetos que envolvam a tecnologia
Microsoft .NET Framework, um estudo será realizado com o foco no
desenvolvimento de uma solução (linguagem e compilador) que implemente os
conceitos da POA e que possa ser usada em projetos comerciais.
1.2.2 Objetivos Específicos
Especificamente este trabalho contempla o desenvolvimento de uma
extensão da linguagem C# voltada para os conceitos da programação orientada a
aspectos. O desenvolvimento desta linguagem abrange os seguintes objetivos
secundários:
Desenvolver o front end [9] de um compilador para a extensão da
linguagem proposta;
Definir as técnicas de entrelaçamento que serão adotadas na
construção do weaver [1] da linguagem;
Definir as estratégias para implementação do back end [9] do
compilador;
Construir um protótipo do compilador com o objetivo de validar os
conceitos apresentados.
6
1.3 Resultados Esperados
Esta monografia terá como impacto principal a criação do ponto de partida
para a implementação funcional de uma extensão orientada a aspectos da
linguagem de programação C#. Espera-se, também, que o desenvolvimento inicial
da ferramenta proposta neste trabalho seja um incentivo para que outros estudantes
e colaboradores produzam novas ferramentas e aprimorarem a ferramenta
desenvolvida com o intuito de difundir a utilização dos conceitos da programação
orientada a aspectos em projetos comerciais envolvendo a plataforma Microsoft
.NET. Estima-se que o produto final deste trabalho dê origem a um projeto público
para a construção de uma versão funcional da linguagem proposta e de ferramentas
que auxiliem o desenvolvimento de aplicações que utilizem esta linguagem.
1.4 Organização do documento
Este trabalho está dividido em 5 capítulos. O primeiro capítulo abordou, em
alto nível, o problema e suas implicações e apresentou a necessidade de haver
linguagens e ferramentas que auxiliem o processo de desenvolvimento orientado a
aspectos na plataforma .NET.
O Capítulo 2 (Fundamentação Teórica) apresenta os principais conceitos
necessários para a compreensão do trabalho proposto. Para tal, o texto aborda as
deficiências da programação orientada a objetos e apresenta os conceitos e
benefícios oriundos da programação orientada a aspectos, e, por fim, apresenta os
conceitos, técnicas e ferramentas relacionadas com o desenvolvimento de
linguagens e compiladores modernos.
No Capítulo 3 (A linguagem D#) é apresentada a extensão da linguagem C#
desenvolvida neste trabalho. Nele são descritos os novos conceitos introduzidos
(aspectos, pontos de junção, pontos de corte, adendos e declarações inter-tipos)
pela extensão da linguagem, enfatizando-se as construções sintáticas da linguagem.
No capítulo 4 (O Projeto do Compilador) são apresentadas as decisões de
projeto realizadas no desenvolvimento do front end do compilador da linguagem,
descrevendo as técnicas e as ferramentas utilizadas na sua construção. São
apresentadas, também, as estratégias definidas para construção do back end do
7
compilador da linguagem, e, por fim, é apresentada uma prova de conceitos, através
da implementação de um protótipo do compilador, com o objetivo de validar as
abordagens apresentadas neste trabalho.
No último capítulo (Conclusão e Trabalhos Futuros), são apresentadas as
considerações finais, realizando-se uma reflexão sobre os resultados obtidos e os
possíveis trabalhos futuros.
8
Capítulo 2
Fundamentação Teórica
À medida que aumenta a complexidade dos sistemas de software surge à
necessidade de melhores técnicas de programação, com o objetivo de organizar e
apoiar o processo de desenvolvimento de software. Dessa forma, essas técnicas de
programação têm evoluído desde construções de baixo nível, como linguagens de
máquina, até abordagens de alto nível, como Programação Orientada a Objetos
(POO) [7]. Aproximadamente 20 anos após o surgimento da POO, o paradigma OO
se tornou dominante no desenvolvimento de software [2,10]. Este paradigma
possibilitou a construção de sistemas particionados em módulos (classes) que
trabalham em conjunto para fornecer funcionalidades específicas, permitindo
maiores níveis de reuso e manutenibilidade [11].
O princípio da separação de interesses (concerns) foi introduzido por Dijkstra
em [12], tendo como objetivo dividir o domínio do sistema em partes menores com o
intuito de entender melhor cada parte isoladamente. De forma geral, os vários
interesses do sistema devem ser separados em módulos de acordo com as
abstrações providas pelas linguagens de programação. Ramnivas em [4] classifica
os interesses de um sistema em:
Interesses do negócio: capturam a funcionalidade central de um
módulo, por exemplo, o procedimento de quitação de uma compra;
Interesses em nível de sistema: capturam requisitos periféricos, no
nível do sistema e que atravessam múltiplos módulos, por exemplo,
segurança, persistência, monitoramento.
No caso da POO, as abstrações básicas para os interesses são classes,
objetos, métodos e atributos. Entretanto, essas abstrações não são suficientes para
separar, em um único módulo, alguns dos interesses contidos em muitos softwares
complexos, tendo assim os comportamentos distribuídos ao longo de vários e, às
vezes não relacionados, módulos. Esses interesses são chamados de interesses
9
transversais (crosscutting concerns - CCC), já que a sua implementação se dá
através da adição de código em diversas classes ao longo do software sem estarem
diretamente relacionados com a funcionalidade definida para estas classes.
Podemos citar como exemplos de interesses transversais: registro de ações,
integridade de transações, autenticação, segurança, desempenho, distribuição,
persistência, monitoramento, etc. [1,3]. A Figura 1 ilustra o mapeamento do interesse
transversal de registro de ações (Logging) em diversos módulos OO.
Figura 1. Mapeamento do interesse transversal de Logging utilizando POO. [Fonte:
adaptado por Soares [2])
Sem o uso de técnicas apropriadas para a separação de interesses e
modularização, alguns fenômenos são observados e podem ser classificados nas
seguintes categorias:
Código Entrelaçado: acontece quando a implementação de um módulo
interage simultaneamente com vários interesses. Na Figura 2 é
apresentado o código fonte do método ChangeEmailAddress que
implementa simultaneamente os interesses de Controle de Transação
(linhas na cor azul), de Tratamento de Exceções (linhas na cor
amarela) e de Requisitos de Negócio (linhas na cor verde);
10
Código Espalhado: acontece quando um interesse é implementado em
múltiplos módulos. Por exemplo, considere o mecanismo de registro de
ações, no servidor Apache TomCat. Na Figura 3 as colunas
representam cada uma das classes do sistema e a linhas grifadas são
referentes à funcionalidade de registro de ações.
Figura 2. Exemplo de código entrelaçado. [Fonte: [24]]
Figura 3. Implementação de registro de ações no servidor Apache Tomcat. [Fonte:
adaptado por Soares [25]]
11
A implementação de códigos entrelaçados e espalhados afetam o
desenvolvimento do software de diversas maneiras:
• Forte acoplamento: métodos das classes primárias precisam conhecer métodos
das classes que implementam funcionalidades dos CCC;
• Fraca coesão: métodos das classes afetadas contêm código que não está
diretamente relacionado a funcionalidades que estas implementam;
• Redundância: muitos fragmentos de código semelhantes ocorrem em diversos
pontos do código-fonte;
• Dificuldades de compreender, manter e reusar: o espalhamento do código da
implementação dos CCC por diversos módulos dificulta a compreensão, manutenção
e reutilização dos módulos do sistema.
Contudo, parte dessas limitações pode ser solucionada com o uso de padrões
de projeto, soluções específicas de domínio, etc.. Por outro lado, existem algumas
extensões do paradigma POO que tentam solucionar suas limitações visando uma
maior modularidade do software, tais como: programação Orientada a Aspectos,
programação Orientada a Sujeito [13] e Programação Adaptativa [14]. Dentre essas
extensões a que tem se mostrado mais promissora é a POA, pois fornece o suporte
para a modularização dos interesses transversais por meio de abstrações que
possibilitam a separação e composição destes interesses na construção dos
sistemas de software. A figura 4 apresenta uma ilustração das implementações de
interesses transversais nos paradigmas OO e OA.
Figura 4. Implementação de CCC nos paradigmas OO e AO. [Fonte: [2]]
12
2.1 Programação Orientada a Aspectos
A POA foi proposta com o objetivo de facilitar a modularização dos interesses
transversais, complementando a POO e aperfeiçoando a arquitetura dos sistemas:
auxiliando, assim, a manutenção dos vários interesses e a compreensão do
software. Em um software, os interesses são implementados em blocos de código,
que manipulam dados. Os interesses que podem ser encapsulados de forma clara
em uma unidade funcional são chamados de componentes. Em POO estes
interesses são modularizados em classes compostas por métodos, que contêm a
implementação do interesse, e por atributos, que compõem os dados manipulados
pelos métodos.
Na POA é introduzido um novo mecanismo para abstração e composição, que
facilita a modularização dos interesses transversais: o aspecto. Desta forma, os
softwares são decompostos em componentes e aspectos. Assim, os requisitos
funcionais normalmente são organizados em componentes, através de uma classe
escrita em uma linguagem OO, como C#, enquanto os requisitos não funcionais,
relacionados às propriedades que afetam o comportamento do sistema, são
organizados em aspectos e são implementados através de uma linguagem OA [1,4].
A POA envolve basicamente três etapas distintas de desenvolvimento:
Decompor os interesses: identificar e separar os interesses
transversais dos interesses do negócio;
Implementar os interesses: implementar cada um dos interesses
identificados separadamente;
Recompor interesses de negócio e transversais: nesta etapa ocorre o
processo de combinação (weaving), que consiste na junção do código
dos componentes de negócio e dos aspectos.
A Figura 5 ilustra as etapas de desenvolvimento da POA:
13
Figura 5. Etapas de desenvolvimento da POA. [Fonte: [2]]
Uma implementação de POA, segundo Kiczales [1], consiste dos seguintes
elementos:
Linguagem de componentes: responsável por implementar interesses do
negócio do sistema de software, por exemplo, C#;
Linguagem de aspecto: suporta a implementação de interesses transversais
de forma clara e concisa, fornecendo meios para construção de estruturas
que descrevam o comportamento do aspecto e definam em que situações
estes ocorrem, por exemplo, AspectJ [4];
Combinador de aspectos: sua tarefa é combinar programas escritos na
linguagem de componentes com os programas escritos na linguagem de
aspectos.
As linguagens de aspectos são classificadas em linguagens de propósito
específico e de propósito geral. Como o próprio nome diz, as linguagens de
propósito específico tratam somente de determinados aspectos, impondo geralmente
algumas restrições quanto ao uso das linguagens de componentes. Por outro lado,
as linguagens de propósito geral permitem a implementação de qualquer tipo de
14
aspecto, sendo de uso mais familiar e de mais fácil adoção pelos desenvolvedores,
uma vez que, geralmente, a linguagem de aspecto compartilha o mesmo ambiente
de desenvolvimento utilizado pela linguagem de componente [1].
Uma linguagem de aspectos de propósito geral é composta, basicamente,
pelos seguintes conceitos e construções:
Aspectos: da mesma forma que a classe é a unidade central de uma
linguagem OO, o aspecto é a unidade central de uma linguagem OA. Os
aspectos encapsulam pontos de corte, adendos e declarações inter-tipos
em uma unidade modular de implementação;
Pontos de junção: são pontos na execução de um programa de
componentes onde os aspectos podem ser aplicados;
Pontos de corte: representa a estrutura responsável por agrupar em apenas
uma declaração diversos pontos de junção;
Adendos: são trechos de código que podem ser adicionados a diversas
partes do sistema através do processo de combinação;
Declarações inter-tipos: são interesses estáticos capazes de produzir
alterações nas classes, interfaces e aspectos do sistema.
2.2 Orientação a aspectos no framework .NET
Atualmente, no framework .NET existem algumas abordagens que dão
suporte a programação orientada a aspectos [8,27,28]. A caracterização dessas
abordagens representa um grande trabalho que foi iniciado por Alan Cyment em
[26]. Entre os principais abordagens listados por Cyment estão: AOP.NET, Aspect#,
Aspect.NET, AspectDNG, Compose*, EOS, NAspect, PostSharp, Rapier LOOM,
SetPoint, SourceWeave.NET, entre outros.
O processo de caracterização dessas abordagens foi realizado através da
listagem de diversas características, tais como: integração com IDE‟s, dependências
de ferramentas, técnicas e estratégias usadas na combinação, tempo de
combinação (estático, carregamento, dinâmico), sintaxe e local de declaração de
15
pontos de corte, modelo de pontos de junção, estratégias de implementação e
instanciação de aspectos, estratégias de implementação de adendos, suporte a
adendos (after, before, around), suporte às declarações inter-tipos, suporte a
exposição de contexto, popularidade do projeto, documentação do projeto, estágio
de desenvolvimento do projeto, propriedade do projeto, caracterização do cenário de
uso principal, etc..
Segundo Cyment [8] o único projeto, dentre os listados acima, com uma
quantidade significante de usuários é o PostSharp [27], os demais ainda não
conseguiram uma quantidade relevante de usuários e a maioria ainda se encontra
em fase de acabamento. Através da caracterização de Cyment é possível inferir
algumas características comuns a maioria das abordagens listadas:
Integração com IDE‟s: a maioria não é integrada a nenhuma;
Estágio do desenvolvimento: a maioria está na versão beta;
Técnica de preweaving: as mais utilizadas são instrumentação de IL e
proxy dinâmico;
Tempo de weaving: prevalece o weaving estático;
Sintaxe de declaração de pontos de cortes: a mais utilizada é
anotações;
Modelo de pontos de junção: a maioria suporta chamadas e execuções
de métodos, escrita e leitura de atributos e propriedades e execuções
de construtores;
Suporte a adendos: a maioria suporta os adendos before, after e
around;
Suporte a declarações inter-tipos: a maioria é restrita a adição de
métodos e atributos;
Implementação dos aspetos: a grande maioria implementa aspectos
como classes;
16
De modo geral, visualiza-se que as abordagens existentes não implementam
todos os conceitos da programação orientada a aspectos e que, de certa forma,
nenhuma abordagem, atualmente disponível, suporta o conceito de herança entre
aspectos e de aspectos e pontos de corte abstratos, dificultando, assim, o reuso de
aspectos.
2.3 Projeto de linguagens de programação
Uma linguagem de programação pode ser considerada como um conjunto de
palavras, operadores, regras sintáticas e semânticas usadas para definir um
programa de computador [9]. Dessa forma, o projeto de linguagens de programação
consiste, basicamente, na definição das palavras chaves da linguagem, dos
operadores, dos tipos de dados que serão manipulados, dos comandos que serão
fornecidos, da estrutura sintática e regras semânticas das sentenças. O projeto de
uma linguagem de programação está intimamente ligado ao projeto de seu
compilador [9]. Dessa forma, podemos considerar que o projeto da linguagem pode
ser considerado como o projeto de seu compilador, pois sem o seu compilador a
linguagem não passa de um simples conjunto de palavras.
2.3.1 Compiladores
Um compilador é um programa que aceita como entrada um código de
programa numa certa linguagem e produz como saída um texto de programa em
outra linguagem, preservando o significado do código de entrada. Esse processo
seria chamado de tradução se os textos estivessem em linguagens naturais. Quase
todos os compiladores fazem a tradução de uma linguagem de entrada, a
linguagem-fonte, para apenas uma linguagem de saída, a linguagem de destino. A
parte de um compilador que executa a análise do texto da linguagem-fonte é
chamada front end [9], e a parte que faz a síntese da linguagem de destino é o back
end [9]. Se o compilador tiver um projeto extremamente limpo, o front end irá ignorar
totalmente a linguagem de destino e o back end irá ignorar totalmente a linguagem-
fonte, o único fator que eles terão em comum será o conhecimento da representação
semântica intermediária.
17
Podemos pensar no processo de compilação como uma sequência de fases,
onde cada fase recebe como entrada o resultado da fase anterior. A entrada inicial é
o programa-fonte e a primeira fase transforma esse programa em uma
representação que possa servir de entrada para a segunda fase. Desta forma, a
cada passagem de uma fase para outra existe uma representação do programa-
fonte. Na prática, entretanto, algumas das fases podem ser agrupadas e a
representação intermediária não precisa ser construída explicitamente. A Figura 6
(próxima página) mostra as fases de compilação.
As três primeiras fases formam a parte de análise da compilação. A análise
quebra o programa-fonte em suas partes constituintes e cria uma representação
intermediária para ele. A fase de análise léxica lê os caracteres do programa-fonte e
os agrupa em um fluxo de tokens. Um token representa um seqüência de caracteres,
logicamente coesa como, por exemplo, um identificador ou uma palavra-chave.
Assim, a primeira fase transforma o programa-fonte em uma seqüência de tokens.
Em seguida, a fase de análise sintática coloca esses tokens em uma estrutura
hierárquica que podemos representar com uma árvore sintática. Por último, a fase
de análise semântica verifica os erros semânticos no programa-fonte e captura
informações de tipo de dados para a fase de geração de código.
Após a fase de análise semântica, alguns compiladores geram uma
representação intermediária explícita do programa-fonte. A representação
intermediária pode ter uma variedade de formas, mas deve atender a dois requisitos:
ser fácil de produzir e ser fácil de traduzir no programa alvo. Dessa forma, a
representação intermediária funciona como um código gerado para uma máquina
abstrata cuja linguagem é mais simples que a linguagem de montagem de uma
máquina real, facilitando, assim, a fase seguinte de geração de código e a possível
portabilidade para outra máquina com linguagem de montagem diferente da
máquina alvo.
18
A fase de otimização tenta melhorar o código intermediário, com o objetivo de
tornar o código mais rápido em tempo de execução. É uma fase importante porque
tem uma participação significativa sobre o desempenho de um programa. Entretanto,
essa fase não é essencial para o processo de tradução. Essa fase simplesmente
está presente na compilação porque geralmente desejamos que o código gerado
pelo compilador possua um bom desempenho.
A fase final de compilador é a geração do código alvo, normalmente, uma
linguagem de montagem. Nessa fase as instruções intermediárias são, cada uma,
traduzidas numa seqüência de instruções de máquina que realizam a mesma tarefa.
Figura 6. Fases de Compilação. [Fonte: [9]]
19
2.3.2 Gramáticas livres de contexto
A Hierarquia de Chomsky [19] (Figura 7) é a classificação de gramáticas
formais descrita em 1959 pelo linguista Noam Chomsky. Esta classificação possui 4
níveis, sendo que os dois últimos níveis (os níveis 2 e 3) são amplamente utilizados
na descrição de linguagem de programação e na implementação de interpretadores
e compiladores. Mais especificamente, o nível 2 é utilizado na análise sintática e o
nível 3 na análise léxica. A classificação das gramáticas começa pelo nível 0, que
possui um maior nível de liberdade em suas regras, e segue até o nível 3, que
possui uma maior quantidade de restrições. Cada nível é um superconjunto do
próximo, logo, uma gramática de nível n-1 é conseqüentemente uma gramática de
tipo n.
Figura 7. Hierarquia de Chomsky. [Fonte: Wikipédia]
Na Hierarquia de Chomsky uma gramática livre de contexto (GLC) G é uma
quádrupla G = (V,∑,P,S) com os seguintes componentes:
V: conjunto finito e não-vazio dos símbolos não-terminais;
∑: conjunto finito e não-vazio dos símbolos terminais (corresponde ao
alfabeto da linguagem definida pela gramática);
P: conjunto finito e não-vazio das regras de produção, no formato α→β,
onde: α ∈ (V) e β ∈ (V U ∑)*;
S: raiz da gramática, S ∈ (V).
20
Dessa forma, uma GLC consiste em um conjunto de regras de produção P.
Cada regra de produção é formada por um símbolo abstrato chamado de símbolo
não-terminal em seu lado esquerdo e por uma seqüência de um ou mais símbolos
não-terminais e terminais em seu lado direito. Os símbolos terminais são símbolos
retirados de um alfabeto especificado por ∑ [9,15]. No contexto do projeto de
compiladores esses símbolos são os tokens provenientes da fase de análise léxica.
Gramáticas são úteis na especificação e compilação de linguagens de
programação, pois são descrições precisas e fáceis de entender. Além disso, o uso
de gramáticas tem outras vantagens que fazem com que ela seja particularmente útil
para projetistas de linguagens de programação e para projetistas de compiladores e
interpretadores. Um gramática, bem projetada, é essencial na tradução correta de
um programa-fonte em código-objeto e também na detecção de erros. Outra
vantagem é a construção automática de analisadores sintáticos que determina se
um programa-fonte está sintaticamente bem formado.
Como já foi mencionado, o uso de gramáticas são muito úteis para projetistas
de compiladores. Por meio delas, os projetistas podem descrever os analisadores
léxicos e sintáticos. O analisador sintático obtém uma seqüência de tokens do
analisador léxico e verifica se essa seqüência pode ser gerada pela gramática da
linguagem-fonte. Além disso, o analisador sintático deve relatar erros de sintaxe
caso a seqüência de tokens lida seja inteligível.
2.3.3 Geradores de analisadores sintáticos
Os geradores de analisadores sintáticos (parser genarators - PG) são
ferramentas que automatizam o processo de criação de reconhecedores de
linguagens, ou seja, eles são programas que geram programas. Seguindo uma
definição formal, podemos dizer que os PG são ferramentas que geram programas
capazes de determinar quando um sentença está em conformidade com
determinada gramática. Os geradores de analisadores sintáticos são extremamente
úteis e largamente utilizados na construção de front ends de compiladores [9,15].
O ANTLR [16] (ANother Tool for Language Recognition) é uma ferramenta
que fornece um arcabouço para a construção de reconhecedores, interpretadores,
21
compiladores e tradutores a partir de descrições gramaticais contendo ações em
uma variedade de linguagens de programação, entre elas C#. O ANTLR oferece um
excelente suporte para a construção de árvores sintáticas, para navegação em
árvores, para tradução, recuperação e comunicação de erros [16,17]. O ANTLR é
um analisador sintático LL e, por definição, utiliza um algoritmo de análise sintática
para um sub-conjunto de gramáticas livre de contexto. Ele é dito um analisador
sintático descendente (top-down) pois tenta deduzir as produções da gramática a
partir do nó raiz. Ele lê a entrada de texto da esquerda para a direita, e expande uma
derivação mais à esquerda. As gramáticas que podem ser analisadas sintaticamente
usando esse tipo de analisador são chamadas gramáticas LL.
Uma analisador LL possui algumas limitações e desvantagens em relação aos
analisadores do tipo LR, entre as principais podemos citar:
Uma gramática LL não pode conter recursividade à esquerda
Uma gramática LL não pode conter ambigüidades;
Um analisador LL detecta um erro sintático na entrada após diversas
expansões, enquanto um analisador LR detecta um erro sintático tão
logo ele aparece na cadeia de entrada ;
A classe de gramáticas que podem ser reconhecidas utilizando os
métodos LR é um superconjunto da classe de gramáticas que podem
ser reconhecidas com os métodos LL.
2.3.4 Backus-Naur Form (BNF) e Extended Backus-Naur Form (EBNF)
A forma de Backus-Naur é uma meta-sintaxe usada para expressar
gramáticas livres de contexto. Uma especificação BNF é um conjunto de regras de
derivação, escritas como:
<símbolo> ::= <termo0> <termo1> | <termo2> | terminal
Onde:
::= : significa “é definida como”;
<símbolo> : é uma regra cujo nome é símbolo;
22
<termo0>,<termo1> e <termo2> : são símbolos não terminais;
| : significa “ou”, ou seja, indica as possibilidades de substituição para regra da
esquerda;
terminal: são símbolos terminais da gramática.
A notação BNF possui algumas deficiências, por exemplo, as repetições não
podem ser diretamente expressas. Em vez disso, é necessário o uso regras
intermediárias com alternativas vazias para produzir uma repetição de forma
recursiva. Outra restrição é a ocorrência dos símbolos reservados (<,>, |,::=), que
não podem ser usados como terminais já que a notação BNF não fornece suporte a
notação entre aspas para representar um terminal.
Para suprir as deficiências da forma BNF foi criada uma extensão
denominada EBNF que provê uma série de extensões:
A cruz de Kleene (+): define uma sequência de um ou mais elementos.
o Ex.: <numero> ::= <digito>+
A estrela de Kleene (*): define uma sequência de zero ou mais
elementos.
o Ex.: <identificador> ::= <letra><alfanumerico>*
Chaves: usadas para agrupar elementos com o intuito de evitar a
criação desnecessária de classes intermediárias.
o Ex.: <identificador> ::= <letra> {<letra><digito>} *
Colchetes: usados para indicar elementos opcionais.
o Ex.: <inteiro> ::= [ + | - ] <inteiro_sem_sinal>
Embora mais concisa a EBNF não é mais poderosa do que a BNF, pois
qualquer gramática definida utilizando EBNF também pode ser representada em
BNF.
23
Capítulo 3
A linguagem D#
A linguagem proposta neste trabalho representa uma extensão orientada a
aspectos da linguagem de programação C#. O nome D# faz alusão à evolução da
linguagem C# e também ao nome do seu criador, Diego. A principal característica
dessa linguagem é a adição, na linguagem C#, dos conceitos da orientação a
aspectos: o aspecto, os pontos de corte, os adendos e as declarações inter-tipos.
3.1 O Aspecto
O aspecto é a unidade do mundo OA responsável por dar suporte aos
mecanismos necessários para o encapsulamento das propriedades do sistema que
não podem ser claramente encapsuladas por uma unidade do mundo OO. É no
aspecto que podemos definir todas as outras estruturas presentes no mundo OA,
entre elas podemos citar: os pontos de corte, os adendos e as declarações inter-
tipos. Na linguagem proposta um aspecto é definido segundo a sintaxe abaixo (uma
referência completa da gramática da linguagem D# encontra-se no apêndice A):
aspect ::= modifiers [perClause] „aspect‟ identifier
[genericTypeList] [{„:‟ {interfaceTypeList [{, baseType}] } |
baseType }] [genericTypeConstraints] „{„ aspectBody ‟}‟
Onde:
modifiers: São palavras reservadas utilizadas para sobrescrever comportamentos
padrão da linguagem. Sua utilização no aspecto consiste, basicamente, na mudança
das características de controle de acesso e de herança. Uma observação deve ser
feita em relação ao reconhecimento, pelo analisador sintático gerado, de algumas
frases não válidas na linguagem D#. Por exemplo, a frase “abstract public protected”
é reconhecida pelo analisador apesar de não ser uma frase válida da linguagem
caracterizando, assim, uma limitação da gramática descrita atualmente. Cada um
dos modificadores aplicáveis à definição do aspecto será descrito abaixo:
24
Modificadores de acesso: São usados para definir a visibilidade do aspecto
em relação ao mundo externo.
o public – O uso deste modificador define que o aspecto é visível em
qualquer ponto exterior (classes e aspectos em diferentes namespace)
a sua definição;
o private – O uso deste modificador define que o aspecto é invisível em
qualquer ponto exterior a sua definição;
o internal – O uso deste modificador define que o aspecto é visível
apenas no namespace e Assembly (arquivo .exe ou .dll) que o define.
(Comportamento Padrão).
Modificadores de herança: São usados para definir regras de hierarquia entre
aspectos.
o abstract – O uso deste modificador define que o aspecto necessita,
obrigatoriamente, definir um ou mais membros abstratos, ou seja, que
necessitam ser sobrescritos pelos seus subtipos. Outra característica,
imposta pelo modificador, é que o aspecto não pode ser instanciado
pelo compilador. Desta forma, apenas os subtipos desse aspecto são
instanciados e entrelaçados com o código alvo;
o sealed – O uso deste modificador define que o aspecto não pode ser
supertipo de nenhum outro aspecto.
perClause: São palavras reservadas utilizadas para definir como um aspecto vai
ser instanciado e associado aos pontos de junção afetados por seus pontos de
corte.
o issingleton – O uso desta cláusula define que apenas uma instância
do aspecto será criado em toda a aplicação;
o perthis (PC) – O uso desta cláusula define que uma instância do
aspecto será criada e associada, caso ainda não exista, a cada objeto
que é o objeto atual sendo executado em todos os pontos de junção
25
afetados pelo ponto de corte PC. A instância do aspecto é elegível para
a coleta de lixo, ao mesmo tempo em que o objeto ao qual está
associado torna-se elegível;
o pertarget (PC) – O uso desta cláusula define que uma instância do
aspecto será criada e associada, caso ainda não exista, a cada objeto
alvo em todos os pontos de junção afetados pelo ponto de corte PC. A
instância do aspecto é elegível para a coleta de lixo, ao mesmo tempo
em que o objeto que está associado torna-se elegível;
o percflow (PC) – O uso desta cláusula define que uma instância do
aspecto será criada e associada, caso ainda não exista, ao fluxo de
execução corrente (na thread em execução) em todos os pontos de
junção afetados pelo ponto de corte PC. O tempo de vida do aspecto
está relacionado até o fim do fluxo de execução e ao seu fim o aspecto
torna-se elegível para a coleta de lixo;
o percflowbelow (PC) – O uso desta cláusula define que uma instância
do aspecto será criada e associada, caso ainda não exista, a cada
fluxo de execução, em cada thread, abaixo de todos os pontos de
junção afetados pelo ponto de corte PC. O tempo de vida do aspecto
está relacionado até o fim do fluxo de execução e ao seu fim o aspecto
torna-se elegível para a coleta de lixo.
identifier: Define um identificador válido da linguagem usado para definir o
nome do aspecto.
genericTypeList: Define um conjunto de tipos genéricos que serão utilizados
para a definição de membros no interior do aspecto. Esses tipos genéricos só podem
ser definidos em aspectos que possuam o modificador abstract, pois todos os tipos
genéricos devem ser substituídos por tipos reais no momento da herança.
interfaceTypeList: Define um conjunto de interfaces que o aspecto deve
implementar.
baseType: Define o supertipo do aspecto.
26
genericTypeConstraints: Define um conjunto de restrições para cada tipo
genérico declarado no aspecto.
Abaixo segue um trecho de código que exemplifica a diferença entre o uso da
cláusula perthis e da cláusula pertarget.
1 public class X{
2 public void M(){
3 StringBuilder sb = new StringBuilder();
4 sb.Write(“o objeto alvo é sb”);
5 sb.Write(“aqui o objeto corrente é sempre uma instância do tipo X”);
6 StringBuilder sb1 = new StringBuilder();
7 sb1.Write(“o objeto alvo agora é sb1”);
8 }
9 }
10 public perthis(p1) aspect A1{
11 public pointcut p1(X x): call( * *.StringBuilder.Write(string)) && within(*.X) && this(x);
12 }
13 public pertarget(p1) aspect A2{
14 public pointcut p1(StringBuilder sb):call( * *.*.Write(string)) && within(*.X) && target(sb);
15 }
Na linha 10 o aspecto A1 é definido utilizando a cláusula perthis indicando
que uma instância do aspecto A1 será criada para cada instância da classe X. Na
linha 13 o aspecto A2 é definido utilizando a cláusula pertarget indicando que uma
instância do aspecto será criada para cada instância da classe StringBuilder
presente no método M da classe X.
Abaixo segue um trecho de código que exemplifica algumas definições de
aspectos na linguagem proposta. O aspecto de nome A1 (linha 1) é definido
utilizando-se a cláusula issingleton, ou seja, apenas uma instância do aspecto será
criada para toda a aplicação. O aspecto A2 (linha 2) exemplifica a definição de um
aspecto abstrato que contém uma lista de tipos genéricos. Os tipos genéricos U e V
possuem uma restrição cada. Dessa forma um aspecto que herde do aspecto A2
deve definir um substituto para o tipo U que implemente a interface IComparable<U>
e um substituto para o tipo V que implemente a interface Interface2. No aspecto A3
(linha 5) é definido um adendo que faz uma referência ao ponto de corte p1 herdado
27
de A2, nessa referência é possível observar a substituição do tipo U, presente na
definição do ponto de corte p1, pelo tipo string.
1 public issingleton aspect A1{ /*corpo do aspecto*/ }
2 public abstract aspect A2<U,V> where U : IComparable<U> where V : Interface2 {
3 protected pointcut p1(U u) : call( * *.*.Split(char sep));
4 /*corpo do aspecto*/
5 }
6 public perthis(p1) aspect A3 : A2<string,int>{
7 before(string str): p1(str){ /*corpo do adendo*/}
8 }
3.2 Pontos de Junção
A linguagem proposta considera o ponto de junção como sendo um lugar bem
definido na estrutura ou no fluxo de execução de um programa, onde
comportamentos adicionais podem ser anexados. Os pontos de junção podem ser
consideradas como eventos e podem existir em qualquer programa, mesmo aqueles
escritos antes da invenção da programação orientada a aspectos. Exemplos de
pontos de junção que ocorrem durante a execução de um programa são uma
chamada feita para um método, a atualização de um campo, a avaliação de uma
expressão, e assim por diante. A figura 8 expõe um diagrama de sequência que
destaca a ocorrência de alguns eventos na execução de um programa. Alguns
desses eventos, tais como as chamadas de método, representam os principais
pontos do programa que podem ser referenciados. Outros eventos, tais como a
execução da quarta linha de código em um determinado método, ou ainda, a
avaliação de uma expressão, foram considerados menos importantes, pois
referências feitas a eles são consideradas frágeis em face da manutenção do
programa e, portanto, não foram considerados como pontos de junção válidos. Um
modelo de pontos de junção determina quais estruturas do programa serão expostas
como pontos de junção e, portanto, podem ser referenciadas pelas expressões
definidas nos pontos de corte, e aquelas que não vão ser expostas. Na linguagem
D# o modelo de pontos de junção proposto expõe os seguintes pontos de junção:
28
Figura 8. Diagrama de sequência ilustrando os pontos de junção na execução de um
programa. [Fonte: [4]]
Chamada de método: Quando um método é chamado;
Execução de método: Quando o corpo de um método é executado;
Chamada de construtor: Quando um objeto é instanciado e seu construtor
inicial é chamado, ou seja, não expõe as chamadas internas aos
construtores base ou this;
Execução de construtor: Quando o corpo de um construtor é executado,
logo após as chamadas internas aos construtores base ou this;
Execução de adendo: Quando o corpo de um adendo é executado;
29
Execução de tratador de exceção: Quando o corpo de um tratador de
exceção é executado;
Execução de inicializador estático: Quando o corpo do construtor estático
de uma classe é executado;
Execução de destrutor: Quando o corpo de um destrutor é executado;
Pré-inicialização de um objeto: Quando o código antes da chamada do
primeiro construtor executa. Em C# os atributos, não estáticos,
inicializados no momento da declaração são atribuídos antes da chamada
do primeiro construtor de um objeto;
Referência a atributo: Quando um atributo não constante é referenciado.
Em C# os atributos constantes são declarados como literais e são
substituídos pelos seus respectivos valores no momento da compilação;
Atribuição a atributo: Quando um valor é atribuído a um atributo;
Referência a propriedade: Quando o corpo do método get de uma
propriedade é executado;
Atribuição a propriedade: Quando o corpo do método set de uma
propriedade é executado.
3.3 Pontos de Corte
Na linguagem D#, o ponto de corte pode ser comparado a uma expressão
regular, pois, similarmente, o ponto de corte define uma expressão que ao ser
avaliada em um determinado alvo produz uma série de correspondências, ou seja,
em nosso contexto, a avaliação dessa expressão em um determinado programa
produz um conjunto de pontos de junção. Na linguagem proposta um ponto de corte
é definido segundo uma das sintaxe abaixo:
30
Ponto de Corte não anônimo (nomeado)
pointcut ::= modifiers „pointcut‟ identifier [genericTypeList]
„(‟ [parameterList] „)‟ [genericTypeConstraints]
[„:‟pointcutExpression] „;‟
Ponto de Corte anônimo
pointcutExpression
Onde:
modifiers: São palavras, reservadas da linguagem, utilizadas para sobrescrever
comportamentos padrão da linguagem. Sua utilização no ponto de corte consiste,
basicamente, na mudança das características de controle de acesso e de herança.
Cada um dos modificadores aplicáveis à definição do ponto de corte será descrito
abaixo:
Modificadores de acesso: São usados para definir a visibilidade do ponto de
corte em relação ao mundo externo.
o public – O uso deste modificador define que o ponto de corte é visível
em qualquer ponto exterior a sua definição;
o private – O uso deste modificador define que o ponto de corte é
invisível em qualquer ponto exterior a sua definição;
o internal – O uso deste modificador define que o ponto de corte é
visível apenas no Assembly que o define. (Comportamento Padrão);
o protected – O uso deste modificador define que o ponto de corte é
visível apenas no aspecto, onde está definido, e nos seus subtipos;
o protected internal – O uso deste modificador define que o ponto de
corte é visível no Assembly que o define e nos subtipos definidos em
outro Assembly.
Modificadores de herança: São usados para definir regras de hierarquia entre
pontos de corte.
31
o abstract – O uso deste modificador define que o ponto de corte não
possui uma expressão, necessitando, portanto, ser sobrescrito nos
subtipos do aspecto que o define;
o sealed – O uso deste modificador define que o ponto de corte não
pode ser sobrescrito;
o virtual – O uso deste modificador define que o ponto de corte pode ser
sobrescrito;
o override – O uso deste modificador define que o ponto de corte
sobrescreve a definição existente no supertipo do aspecto em questão.
identifier: Define um identificador válido da linguagem usado para definir o
nome do ponto de corte.
genericTypeList: Define um conjunto de tipos genéricos que serão utilizados
para a definição da lista de parâmetros do ponto de corte. Esses tipos genéricos só
podem ser definidos em pontos de corte não anônimos.
ParameterList: Define um conjunto de parâmetros que serão usados na definição
de uma adendo. Esses parâmetros só podem ser definidos em pontos de corte não
anônimos.
genericTypeConstraints: Define um conjunto de restrições para cada tipo
genérico declarado no ponto de corte. Essas restrições só podem ser definidas em
pontos de corte não anônimos.
pointcutExpression: São expressões utilizadas para definir agrupamentos de
pontos de junção. Na linguagem proposta uma expressão de ponto de corte é
definida segundo a sintaxe abaixo:
pointcutExpression ::= orPointcutExpr {„&&‟ orPointcutExpr}*
orPointcutExpr ::= unaryPointcutExpr {„||‟ unaryPointcutExpr}*
unaryPointcutExpr ::= „!‟ unaryPointcutExpr | basicPointcutExpr
32
basicPointcutExpr ::=
„(‟ pointcutExpression „)‟|
pointcutname|
get„(‟signature „)‟|
set„(‟signature „)‟|
fget„(‟signature „)‟|
fset„(‟signature „)‟|
call„(‟signature „)‟|
adviceexecution„(‟„)‟|
within„(‟typePattern „)‟|
handler„(‟typePattern „)‟|
withincode„(‟signature „)‟|
this„(‟type | identifier„)‟|
target„(‟type | identifier„)‟|
initialization„(‟signature „)‟|
cflow„(‟pointcutExpression „)‟|
preinitialization„(‟signature „)‟|
cflowbelow„(‟pointcutExpression „)‟|
destructorexecution„(‟typePattern „)‟|
staticinitialization„(‟typePattern „)‟|
args„(‟{type|identifier}{„,‟ type|identifier}*„)‟
Onde:
typePattern: Define um padrão para a identificação de um tipo. Esse padrão é
definido seguindo a sintaxe abaixo.
33
{identifier | “*” }{“.” identifier | “*”}* {“.”identifier[“+”]|“*”}
Onde:
identifier: Define a sintaxe para um identificador da linguagem.
“*”: Define um caractere coringa que é equivalente a qualquer identificador da
linguagem.
“.”: Define o separador de espaço de nomes.
“+”: Define um caractere coringa que representa todos os subtipos do tipo definido
pelo identificador que o precede na expressão.
signature: Define um padrão para a identificação de membros de tipos, ou seja,
atributos, métodos, propriedades, etc.. Esse padrão é definido seguindo a sintaxe
abaixo.
{type|identifier|“*”} typePattern {“.” identifier|“*”}
“(” “...” | __arglist | {{{type [indetifier]} | “*”}{“,” {{type
[identifier]}| “*”}}*}“)”
Onde:
type: Define a sintaxe para um tipo de dados da linguagem.
“...”: Define um sequência de caracteres coringa que representa qualquer
combinação de parâmetros em um chamada.
__arglist: Define a palavra reservada da linguagem C# que representa um lista
de argumentos de tamanho variável.
“||”: Define a disjunção lógica de expressões de ponto de corte.
“&&”: Define a conjunção lógica de expressões de ponto de corte.
“!”: Define a negação lógica de expressões de ponto de corte.
pointcutname: Define o identificador de um ponto de corte.
34
adviceexecution(): Define a sintaxe para a captura de todas as execuções de
qualquer adendo.
destructorexecution(typePattern): Define a sintaxe para a captura de todas
as execuções do destrutor dos objetos cujo tipo corresponde ao padrão definido por
typePattern.
call(signature): Define a sintaxe para a captura de todas as chamadas de
métodos ou construtores cuja assinatura corresponde ao padrão definido por
signature.
execution(signature): Define a sintaxe para a captura de todas as execuções
de métodos ou construtores cuja assinatura corresponde ao padrão definido por
signature.
fget(signature): Define a sintaxe para a captura de todas as referências a
atributos cuja assinatura corresponde ao padrão definido por signature.
fset(signature): Define a sintaxe para a captura de todas as atribuições
realizadas em atributos cuja assinatura corresponde ao padrão definido por
signature.
get(signature): Define a sintaxe para a captura de todas as execuções do
método get em propriedades cuja assinatura corresponde ao padrão definido por
signature.
set(signature): Define a sintaxe para a captura de todas as execuções do
método set em propriedades cuja assinatura corresponde ao padrão definido por
signature.
handler(typePattern): Define a sintaxe para a captura de todas as execuções
de uma tratador de exceção cujo tipo da exceção corresponde ao padrão definido
por typePattern.
initialization(signature): Define a sintaxe para a captura de todos os
pontos de junção situados após a chamada do primeiro construtor dentro de um
construtor cuja assinatura corresponde ao padrão definido por signature.
35
preinitialization(signature): Define a sintaxe para a captura de todos os
pontos de junção situados antes da chamada do primeiro construtor dentro de um
construtor cuja assinatura corresponde ao padrão definido por signature.
staticinitialization(typePattern): Define a sintaxe para a captura da
execução de todos os construtores estático cujo tipo corresponde ao padrão definido
por typePattern.
within(typePattern): Define a sintaxe para a captura de todos os pontos de
junção existentes em um tipo cujo nome corresponde ao padrão definido por
typePattern.
withincode(signature): Define a sintaxe para a captura de todos os pontos de
junção existentes dentro de um construtor ou método cuja assinatura corresponde
ao padrão definido por signature.
cflow(pointcutExpression): Define a sintaxe para a captura de todos os
pontos de junção dentro do fluxo de execução de cada ponto de junção P afetado
pela expressão pointcutExpression.
cflowbelow(pointcutExpression): Define a sintaxe para a captura de todos
os pontos de junção localizados abaixo do fluxo de execução de cada ponto de
junção P afetado pela expressão pointcutExpression.
this(type | identifier): Define a sintaxe para a captura de todos os pontos de
junção onde o objeto corrente é uma instância do tipo definido por type ou do tipo
do argumento definido por identifier.
target(type | identifier): Define a sintaxe para a captura de todos os pontos
de junção onde o objeto alvo é uma instância do tipo definido por type ou do tipo
do argumento definido por identifier.
args(type | identifier {“,” type | identifier}*): Define a sintaxe para a
captura de todos os pontos de junção onde os argumentos são instâncias do tipo
definido por Type ou do tipo do argumento definido por identifier.
36
Abaixo segue um trecho de código que exemplifica a definição de um aspecto
e de pontos de corte na linguagem proposta.
1 public abstract issingleton aspect A1<T>{
2 public pointcut P1(string msg): call(System.Console.*(string msg));
3 private pointcut P2(): execution(Sytem.IO.Stream+.Write(...));
4 public pointcut P3(int count): set (System.Collection.Generic.IList+.Count);
5 public abstract pointcut P4(T t);
6 protected pointcut P5(): handler (System.Data.Common.DbException+);
7 pointcut P6(): call( *.*.Jump(decimal) && args(decimal height) && within(*.*.Person)
8 public pointcut P7<U,V>(U u, V v) where U: IComparable<U>: call(* *.*.CompareTo(*));
9 protected virtual pointcut P8(T t): destructorexecution() && this(t);
10 }
11 public issingleton aspect A2 : A1<string>{
12 public pointcut P1(): set(* *.Conta.Saldo) && cflow(execution(* *.Fachada.Saque(…)));
13 private pointcut P2(): fget(* *.Conta+.saldo) && this(*.ContaCorrente);
14 internal pointcut P3(): call(* *.*.Credito(double valor) && target(*.Poupanca);
15 public override pointcut P4(string str): execution(* *.*.Split(…)) && this(str);
16 }
No trecho acima o ponto de corte P1 (linha 2) define uma expressão do tipo
call que intercepta todas as chamadas de métodos da classe System.Console que
possuem um parâmetro do tipo string. Na linha 3 o ponto de corte P2 captura todas
as execuções dos métodos Write, que possuam qualquer combinação de
parâmetros, da classe Stream e de suas classes filhas. No ponto de corte P3 (linha
4) é definida uma expressão que captura todas as execuções do método set da
propriedade Count presente em todas as classes que implementam a interface IList.
Na linha 5 é definido um ponto de corte abstrato cujo nome é P4, nesse ponto
de corte não é definida um expressão de ponto de junção, sendo obrigatória a
definição da mesma nos aspectos que herdam do aspecto A1. Para tanto o aspecto
deve definir um ponto de corte não abstrato com o mesmo nome e com o
modificador override que simboliza a ação de sobrescrita da expressão de ponto de
junção. No ponto de corte P5 (linha 6) é definida uma expressão que captura a
37
execução de todos tratadores de exceções cujo tipo da exceção tratada corresponde
a um subtipo da classe System.Data.Common.DbException.
Na linha 7, o ponto de corte P6 define uma expressão do tipo call que
intercepta todas as chamadas do método Jump que estejam contidas dentro da
classe Person e que possuam um argumento do tipo decimal. Na linha 8 é definido
o ponto de corte P7 que declara uma lista de tipos genéricos que são usados para
definir a sua lista de parâmetros. Neste caso o tipo genérico U possui uma restrição
que define que os tipos reais que substituírem o tipo U devem implementar a
interface IComparable<U>. Na linha 9 o ponto de corte P8 define uma expressão
que captura todas as execuções de destrutores da classe T definida como um tipo
genérico na definição do aspecto.
Na linha 12, o ponto de corte P1 define uma expressão que captura todas as
atribuições realizadas sobre a propriedade Saldo da classe Conta que estejam no
fluxo de execução de um método com o nome Saque da classe Fachada. Na linha
13 é definido o ponto de corte P2 que declara uma expressão que captura todas as
referências realizadas ao atributo saldo da classe ContaCorrente. Na linha 14, o
ponto de corte P3 define uma expressão que captura todas as chamadas ao método
Creditar da classe Poupanca. Por fim, na linha 15 é definido um ponto de corte P4
que sobrescreve a definição do ponto de corte P4 do aspecto abstrato P1. Esse
ponto de corte define uma expressão que captura todas as execuções de um
método Split da classe string.
3.4 Adendos
Como visto anteriormente o ponto de corte pode ser considerado como um
predicado que agrupa um ou mais pontos de junção sem, no entanto, realizar
nenhuma modificação sobre eles, necessitando, assim, de um componente adicional
que realize esta função. Esse componente na programação orientada a aspectos é
denominado adendo.
Na linguagem proposta, cada adendo está associado a um ponto de corte,
nomeado ou anônimo, e especifica o comportamento que deve ser executado antes,
38
depois, ao redor ou em substituição, aos pontos de junção afetados pelo ponto de
corte ao qual está associado.
Uma declaração de adendo pode conter parâmetros cujos valores podem ser
referenciados no corpo do adendo. Ao contrário das chamadas de método, onde os
valores dos parâmetros são passados explicitamente pelo chamador, os valores dos
parâmetros do adendo são fornecidos pelo ponto de corte ao qual está associado.
Como o adendo é sempre invocado implicitamente (Figura 9), não há necessidade
de nomeá-lo e, portanto, ele não pode ser chamado explicitamente através do seu
nome, ou nem por qualquer outro meio. Pela mesma razão, não existem
modificadores de acesso para o adendo. Na linguagem proposta um adendo é
definido segundo a sintaxe abaixo:
advice ::= [type] adviceType „(‟[parameterList]„)‟
[{returnEvent[„(‟returnType „)‟]}] „:‟ pointcutExpression
„{‟adviceBody „}‟
Onde:
type: Define o tipo de retorno em adendos dos tipos around e instead. Os adendos
dos tipos before e after não declaram um tipo de retorno.
adviceType: Define a forma como o adendo vai ser vinculado aos ponto de junção
afetados pela expressão pointcutExpression. Na linguagem proposta os tipos
possíveis para um adendo são:
before: Define que o adendo deve ser executado antes do ponto de
junção afetado;
after: Define que o adendo deve ser executado após o ponto de junção;
around: Define que o adendo deve ser executado ao redor do ponto de
junção, ou seja, este tipo de adendo possui o controle sobre a execução
do ponto de junção ao qual está vinculado, podendo ou não executá-lo
através da palavra reservada proceed;
39
instead: Define que o adendo substitui o comportamento do ponto de
junção. Seu comportamento é semelhante ao adendo around sem a
chamada do método proceed.
Figura 9. Diagrama de sequência ilustrando a invocação implícita de adendos.[Fonte: [4]]
parameterList: Define a lista de parâmetros do adendo.
returnEvent: Define o evento que dispara o retorno do ponto de junção . Esse
modificador é utilizado, apenas, nos adendos do tipo after, para restringir a execução
do adendo ao tipo de retorno definido por returnEvent. Na linguagem proposta dois
tipos de retorno são definidos:
throwing: Define que o adendo é executado quando o ponto de junção
retorna através de uma exceção;
returning: Define que o adendo é executado quando o ponto de junção
retorna com sucesso.
40
returnType: Define o tipo e o identificador da variável que representa o retorno ou
a exceção lançada pelo ponto de junção. Essa variável pode ser acessada em
adendos do tipo after.
pointcutExpression: Define o pointcut, anônimo ou nomeado, ao qual o adendo
será vinculado.
adviceBody: Define os comandos presentes no corpo do adendo.
Abaixo segue um trecho de código que exemplifica a criação de um aspecto
completamente funcional na linguagem proposta.
1 public issingleton aspect MeuTerceiroAspecto{
2 public pointcut NamespaceXCalls(): execution(public NamespaceX.*.*(…));
3 before () : NamespaceXCalls {
4 Log.Save(...);
5 }
6 void instead (string msg) :
7 call(System.Console.Write(string msg)) && within (NamespaceX.*.*) {
8 MessageBox.Show (msg);
9 }
10 after () throwing (System.Data.Common.DbException e): NamespaceXCalls {
11 ErrorLog.Save(e);
12 }
13 private pointcut ExecComando() :execution(int Sytem.Data.Common.DbCommand+.*(...)
14 after() returning(int affectedRows): ExecComando {
15 System.Console.Write(affectedRows + “ foram afetadas pelo comando”);
16 }
17 int around(): ExecComando {
18 int affectedRows = proceed();
19 System.Console.Write(affectedRows + “ foram afetadas pelo comando”);
20 return affectedRows;
21 }
22 }
No trecho acima o adendo do tipo before definido na linha 3 exemplifica a
adição da operação de log, através da chamada do método Save(...) da classe Log,
em todas as execuções de métodos do espaço de nomes NamespaceX. Na linha 6 o
41
adendo do tipo instead exemplifica a troca das chamadas ao método Write da classe
Console, contidas dentro das classes do espaço de nomes NamespaceX, pela
chamada do método Show da classe MessageBox. Esse cenário representa a troca
de uma interface baseada em console por uma interface baseada em janelas
gráficas.
Na linha 10 o adendo do tipo after throwing exemplifica o registro, através do
método Save da classe ErrorLog, de todos os erros ocorridos durante a execução de
qualquer método de uma das classes contidas dentro do espaço de nomes
NamespaceX e que foram ocasionados por uma exceção do tipo
System.Data.Common.DbException proveniente da comunicação com um banco de
dados.
Na linha 14 é definido uma adendo to tipo after returning que imprime a
quantidade de linhas afetadas pela execução de qualquer método, que possua o
retorno do tipo int, das subclasses da classe DbCommand. Por fim, na linha 17 é
definido um adendo do tipo around que exemplifica a chamada ao ponto de junção
ao qual está vinculado através da palavra reservada proceed.
3.5 Declarações Inter-Tipos
Na linguagem D#, declarações inter-tipos, também denominadas
introductions, podem ser usadas para fornecer definições de campos, propriedades,
métodos, construtores, destrutores, eventos, etc. em nome de outros tipos. Elas
também podem ser usadas para implementar interfaces, declarar supertipos,
declarar precedência entre aspectos e declarar novos erros e avisos de compilação.
Na linguagem proposta, as declarações inter-tipos são classificadas em declarações
de membros, de hierarquia, de precedência e de erros e avisos.
3.5.1 Declarações de membros
São utilizadas para adicionar novos membros a uma ou mais classes em
tempo de compilação. Na linguagem proposta as declarações inter-tipos de
membros são definidas segundo a sintaxe abaixo:
42
„insert‟ „into‟ „:‟ typePattern „{‟ body „}‟
Onde:
typePattern: Define um padrão para a identificação dos tipos que serão alvo da
declaração inter-tipo.
body: Define os membros que serão adicionados aos tipos afetados pela declaração
inter-tipo. Caso os membros definidos na declaração já estejam definidos no tipo
alvo acontecerá um erro em tempo de compilação.
3.5.2 Declarações de Hierarquia
São utilizadas para declarar novas regras de hierarquia, tais como, uma nova
superclasse ou a adição de novas interfaces. Na linguagem proposta, as
declarações inter-tipos de hierarquia são definidas segundo uma das sintaxe abaixo:
Declaração de superclasse
„declare‟ „parent‟ „:‟ typePattern „:‟ type„;‟
Onde:
typePattern: Define um padrão para a identificação dos tipos que serão
alvo da declaração inter-tipo.
type: Define a nova superclasse, que substituirá a anterior, para os tipos
afetados pela declaração inter-tipo. A nova superclasse deve ser uma
superclasse da classe já anteriormente declarada como classe mãe.
Declaração de interfaces
„declare‟ „interfaces‟ „:‟ typePattern „:‟ typeList „;‟
Onde:
typePattern: Define um padrão para a identificação dos tipos que serão
alvo da declaração inter-tipo.
typeList: Define novas interfaces que o tipo passará a implementar. As
interfaces anteriormente declaradas ainda permanecem válidas.
43
3.5.3 Declarações de Precedência
São utilizadas para declarar a ordem de precedência no processo de
entrelaçamento dos aspectos com o código alvo. Na linguagem proposta, as
declarações inter-tipos de precedência são definidas segundo a sintaxe abaixo:
„declare‟ „precedence‟ „:‟ typeList „;‟
Onde:
typeList: Define a ordem dos aspectos no processo de entrelaçamento.
3.5.4 Declarações de Erros e Avisos
São utilizadas para declarar novos erros e avisos em tempo de compilação.
Na linguagem proposta, as declarações inter-tipos de hierarquia são definidas
segundo a sintaxe abaixo:
„declare‟ {„error‟ | „warning‟} pointcutExpression „:‟ message „;‟
Onde:
pointcutExpression: Define o pointcut, anônimo ou nomeado, ao qual a
declaração será vinculada. Apenas os pontos de junção que possam ser
identificados em tempo de compilação gerarão erros e avisos.
message: Define a mensagem de erro ou aviso que será exibida quando o
compilador identificar um ponto de junção afetado pela expressão defina por
pointcutExpression.
Abaixo segue um trecho de código que exemplifica a definição de um aspecto
e de declarações inter-tipos na linguagem proposta.
1 public issingleton aspect MeuQuartoAspecto{
2 insert into : Entidades.* {
3 public string SerializedName { get { return “_” + this.GetType().FullName;} }
4 public string Serialize(){
5 return ObjectSerializer.Serialize(this);
6 }
7 }
44
8 declare parent : Entidades.* : EntidadeBase;
9 declare interfaces : Entidades.* : ISerializable;
10 declare precedence : MeuQuartoAspecto, MeuTerceiroAspecto;
11 declare warning : get (Entidades.*.SerializedName) &&
12 !withincode(*.ObjectSerializer.*(…) : “use apenas em ObjectSerializer”;
13 }
No trecho acima a declaração inter-tipo do tipo insert into definida na linha 2
exemplifica a adição da propriedade SerializedName e do método Serialize em todas
as classes presentes no espaço de nomes Entidades. Na linha 9 a declaração inter-
tipo do tipo declare interfaces define que todas as classes do espaço de nomes
Entidades implementam a interface ISerializable. Este cenário, composto pelas
declarações da linha 2 e da linha 9, exemplifica, de formar simplificada, a adição de
membros com o objetivo de fornecer a capacidade de serialização necessária para
implementar, por exemplo, o CCC de distribuição.
Na linha 8 a declaração inter-tipo do tipo declare parent define que todas as
classes do espaço de nomes Entidades são subtipos da classe EntidadeBase. Na
linha 10 a declaração de precedência entre aspectos define que o aspecto
MeuQuartoAspecto deve ser aplicado antes do aspecto MeuTerceiroAspecto, ou
seja, todas as declarações realizadas no aspecto MeuTerceiroAspecto não serão
visíveis no aspecto MeuQuartoAspecto. Por fim, na linha 11 a declaração inter-tipo
do tipo declare warning define um novo aviso de compilação que exibe a mensagem
“use apenas em ObjectSerializer ” sempre que uma referência a propriedade
SerializedName, das classes presentes no espaço de nomes Entidades, for
realizada fora do escopo dos métodos da classe ObjectSerializer.
45
Capítulo 4
O Projeto do Compilador
No projeto de um compilador para uma linguagem de alto nível diversas
decisões e estratégias são adotadas na concepção de cada componente do
compilador da linguagem, estas decisões fornecem a base para o desenvolvimento
detalhado das fases de análise léxica, sintática, semântica, geração de código e de
combinação. Dessa forma, é de fundamental importância a discussão das principais
decisões e estratégias envolvidas na construção do compilador da linguagem D#.
4.1 Análise Léxica e Sintática
As fases de análise léxica e sintática são organizadas em torno da sintaxe da
linguagem a ser compilada [9]. A sintaxe de uma linguagem de programação
descreve através de uma gramática livre de contexto a estrutura de frases que
formam sentenças válidas em um programa, enquanto a semântica da linguagem
define o que seus programas significam, ou seja, o que cada programa faz quando é
executado. Para especificar a sintaxe, apresentamos, no capítulo 2, uma notação
bastante utilizada, denominada gramática livre de contexto. Desta forma, podemos
considerar que o projeto de um compilador se inicia com o projeto da gramática da
sua linguagem fonte, e que uma das principais decisões no projeto de um
compilador consiste em definir as estratégias e ferramentas que serão utilizadas no
processo de construção dos analisadores léxico e sintático (front end) para a
gramática alvo.
Segundo Aho [9] e Dick [15] a utilização de geradores de analisadores
sintáticos é, atualmente, a melhor abordagem para construção de analisadores
léxicos e sintáticos, pois estes geradores são capazes de produzir analisadores
altamente eficientes a partir de gramáticas livres de contexto que podem ser
descritas com certa facilidade. A partir desse ponto a questão torna-se mais sutil,
46
podendo resumir-se a qual gerador utilizar. Em nosso caso a escolha da ferramenta
a ser utilizada foi baseada nos seguintes critérios:
IDE da ferramenta: foi analisado o suporte gráfico fornecido pela
ferramenta, o suporte à refatoração, o suporte à depuração, etc. De uma
forma mais abrangente foi considerado o nível de interatividade que a
ferramenta fornece ao projetista da linguagem;
Linguagem alvo: foi analisado o suporte para geração de código para
diferentes linguagens de programação, inclusive C#;
Documentação: foi analisada a diversidade das fontes de documentação
existentes;
Interação entre analisador léxico e sintático: foi analisado a simplicidade
da comunicação entre os analisadores;
Gramática da linguagem C#: foi analisada a existência de alguma
especificação, pública, da gramática da linguagem C# que poderia ser
facilmente portada para a ferramenta alvo.
Nas tabelas 1 e 2 é realizada uma comparação entre algumas ferramentas
que possuem suporte a linguagem alvo C#. Em [31] é possível encontrar uma
comparação mais extensa entre os geradores de analisadores sintáticos atualmente
disponíveis.
Tabela 1. Comparação dos geradores de analisadores sintáticos
Nome do
gerador
LL
ou
LR
IDE Documentação Interação entre
analisador léxico
e sintático
Linguagens alvo Gramática C#
ANTLR LL Interface gráfica com
editor, interpretador,
gerador de diagramas
e debugger
Possui ampla
documentação em
fóruns, sites e
livros
Analisador
léxico e sintático
integrados
Java,C,C++,C#,D,
ObjectiveC, Python,
Ruby,Perl,PHP,Ada
95,ActionScript,Java
Script
Foi
encontrada
uma versão
completa em
[18]
GPPG LR baseada em console Possui
documentação em
fóruns e sites
Integrado com o
analisador léxico
GPLEX
C# Nenhuma
versão foi
encontrada
47
Tabela 2. Comparação dos geradores de analisadores sintáticos
Nome do
gerador
LL
ou
LR
IDE Documentação Interação entre
analisador léxico
e sintático
Linguagens alvo Gramática C#
SableCC LR baseada em console Possui ampla
documentação
em fóruns, sites
e livros
Possui ampla
documentação
em fóruns, sites
e livros
Analisador léxico e
sintático integrados
Foi
encontrada
uma versão
inicial em [29]
GOLD LR Interface gráfica com
editor, Interpretador e
visualizador de estado
Possui ampla
documentação
em fóruns e
sites
Analisador
léxico e sintático
integrados
ANSI C,C#,C++,D,
Delphi,Java,Pascal,
Python, todas as
linguagens .NET
Nenhuma
versão foi
encontrada
Coco/R LL baseada em console Possui
documentação
em fóruns e
sites
Analisador
léxico e sintático
integrados
C, C++, C#, F#,
Java, Ada, Pascal,
Modula, Oberon,
Ruby, Unicon,
Visual Basic .NET
Foi
encontrada
uma versão
inicial em [30]
CSTools LR baseada em console Possui
documentação
em fóruns e
sites
Analisador
léxico e sintático
integrados
C# Nenhuma
versão foi
encontrada
De acordo com os critérios analisados, optamos pela escolha da ferramenta
ANTLR, mencionada no capítulo 2, que atende satisfatoriamente todos os critérios.
Apesar da desvantagem de ser uma gerador LL o ANTLR possui uma IDE, o
ANTLRWorks, com suporte a edição avançada de gramáticas, a visualização de
diagramas de produções da linguagem, a depuração do processo de
reconhecimento de uma sentença, a interpretação de sentenças, etc. A Figura 10
apresenta a tela de edição do ANTLRWorks.
Após a definição de qual PG (Parse Generator) será utilizado no projeto da
linguagem, é necessária a definição das entradas (gramática) e saídas (estrutura
intermediária) do gerador. Como o objetivo deste trabalho é produzir uma extensão
da linguagem C#, optou-se por partir de uma definição de gramática desta
linguagem. Após uma fase de análise e testes, foi adotada como implementação
48
inicial a gramática encontrada em [18]. Essa gramática contempla, em parte, a
especificação 4.0 da linguagem C# e, em sua totalidade, a especificação da versão
3.0. Graças à escolha desse ponto de partida o escopo do desenvolvimento da
gramática, pode ser resumido na definição das novas construções, descritas no
capítulo 3, necessárias para a incorporação dos conceitos da POA em C#.
Figura 10. A ferramenta ANTLRWorks [Fonte: próprio autor]
Segundo Aho [9] e Dick [15], a utilização de representações intermediárias na
comunicação entre os componentes de um compilador é de extrema importância, e
de acordo com as recomendações de Aho [9], Dick [15] e Parr [16,17] o uso de
árvores sintáticas abstratas (AST – Abstract Sintax Tree) para comunicação entre os
analisadores sintático e semântico é uma ótima solução. Dessa forma utilizamos a
opção (output=AST) no ANTLR em conjunto com a técnica de reescrita [16]
(rewriter) de produções para produzir uma estrutura intermediária baseada em
árvores abstratas.
49
4.2 Análise Semântica
São inúmeras as regras semânticas em uma linguagem OA de alto nível e de
propósito geral, podemos citar como exemplos, as seguintes:
Checagem das regras de herança nas declarações de aspectos (palavras
reservadas abstract e sealed);
Checagem do tipo de retorno de um adendo em relação ao respectivo
ponto de corte;
Checagem da correspondência de parâmetros entre adendos e seus
respectivos pontos de corte;
Checagem de conflitos entre declarações inter-tipos de membros;
Checagem de conflitos de supertipo em declarações inter-tipos de
herança, etc..
Neste trabalho optou-se por implementar a análise semântica da linguagem
utilizando um esquema de tradução dirigido por sintaxe em conjunto com técnicas
baseadas em padrões de projeto OO, tais como, Visitor e Builder [20]. Dessa forma
pretende-se validar todas as regras semânticas através de uma sequência de
caminhamentos, sobre a árvore abstrata produzida na fase de análise sintática,
utilizando como base o padrão de projeto visitor. Para tanto, serão criadas classes
visitadoras, uma para cada regra semântica implementa. De forma que o processo
de validação semântica consistirá, basicamente, na execução dos métodos de
caminhamento para todas as classes visitadoras.
Até o presente momento, foram implementadas duas regras semânticas:
Checagem do tipo de retorno de um adendo em relação ao respectivo
ponto de corte;
Checagem da correspondência de parâmetros entre adendos e seus
respectivos pontos de corte;
50
O objetivo da implementação dessas duas regras semânticas é validar a
estrutura de análise semântica proposta neste trabalho. Na figura 11 é apresentada
a estrutura montada para realizar a validação.
Figura 11. Estrutura da implementação do padrão Visitor. [Fonte: próprio autor]
Essa estrutura é composta pelos seguintes participantes:
SemanticRule (Abstract Visitor): Declara uma operação de visita para cada
tipo de nó presente na estrutura de objetos.
AdviceParametersCheckRule e AdviceReturnTypeRule (Concret Visistors):
Implementa cada operação declarada pela classe SemanticRule. Cada
operação implementa um fragmento do algoritmo definido para a classe
correspondente na estrutura de objetos.
BaseTree (Abstract Element): Define uma operação accept() que recebe
uma SemanticRule.
51
PointcutTree e AdviceTree (Concret Elements): Implementa uma operação
accept() que recebe uma SemanticRule e chama a operação de visita
apropriada deste visitador.
Nessa estrutura o cliente (analisador semântico) deve criar o Visitor
correspondente a cada regra semântica e visitar todos os elementos da estrutura de
objetos com este Visitor. Esse processo de visita é realizado através da chamada ao
método accept de cada elemento da estrutura de objetos e estes, por sua vez,
chamam o método de visita correspondente a sua implementação.
4.3 Geração de Código
Esta fase tem como principal objetivo a geração de código dos aspectos do
sistema. Para tanto o gerador de código recebe como entrada a representação
intermediária dos aspectos, no formato de uma AST, produzida pelo front end do
compilador e através de um processo de tradução produz como saída um conjunto
de classes que representam cada aspecto do sistema. Esse processo de tradução é
realizada em duas etapas: a primeira produz uma representação intermediária das
construções presentes no aspecto e a segunda transforma essas representações
intermediárias em classes C#. Essas classe contém as definições dos adendos e
das declarações inter-tipos dos aspectos. Enquanto que os pontos de corte
permanecem em formato intermediário, pois eles não são mapeados para nenhuma
estrutura do mundo OO. Atualmente o gerador de código dá suporte a geração de
adendos do tipo after e before e declarações inter-tipos de membros que inserem
atributos e propriedades.
4.4 Combinação
A última fase do compilador proposto é a combinação entre aspectos e código
de componentes. Para uma linguagem orientada a aspectos o projeto de
combinador deve começar pela decisão de qual estratégia de combinação, entre
aspectos e componentes, será utilizada. Na introdução original da POA, Kiczales [1]
52
listou as seguintes possibilidades para a combinação dos aspectos com o código de
componentes:
um pré-processador do código fonte (similar as implementações originais
do C++), que executaria a combinação dos aspectos ainda no nível da
linguagem fonte;
um pós-processador que incluiria patches em arquivos binários;
um compilador que suportasse a POA e gerasse arquivos binários com
os adendos inseridos;
em tempo de carregamento, os adendos seriam inseridos no momento em
que as classes fossem carregadas pelo ambiente de execução;
em tempo de execução todos os pontos de junção seriam detectados pelo
ambiente de execução e entrelaçados com o código dos adendos. O uso
dessa implementação implicaria na modificação da estrutura interna da
CLR (Common Language Runtime) [6].
Em Cohen e Gil [21] foi introduzida uma abordagem em tempo de instalação
(deploy-time weaving) utilizadas em servidores web J2EE. Basicamente a técnica
consiste em um pós-processador, mas ao invés de aplicar patches ao código
gerado, os criadores sugerem a criação de subclasses das classes existentes,
fazendo com que as modificações sejam inseridas através de métodos sobrescritos.
Em nosso caso a escolha da abordagem a ser utilizada foi baseada nos
seguintes critérios:
Simplicidade do processo de desenvolvimento: as abordagens que
utilizam pré e pós processadores de código não fornecem suporte para o
desenvolvimento de ferramentas que auxiliem os processos de edição e
depuração do código combinado;
Desempenho de execução do código combinado: as abordagens que
adiam o processo de combinação, geralmente, causam um impacto no
desempenho da execução do código.
De acordo com os critérios mencionados, optou-se pela escolha da
abordagem baseada na construção de um compilador, pois atende perfeitamente
todos os critérios. Atualmente, o combinador de aspectos dá suporte a combinação
dos pontos de corte dos tipos call e execution.
53
4.5 Prova de Conceito
As decisões de projeto descritas nas seções anteriores serviram como base
para construção do protótipo do compilador da linguagem D#. Esse protótipo foi
construído usando a linguagem C#, a ferramenta Visual Studio 2010 e a ferramenta
ANTLRWorks.
4.5.1 Estrutura do protótipo do compilador
Na Figura 12 as classes com a terminação Definition definem as
representações intermediarias produzidas após a fase de geração de código e as
classes com a terminação Builder são responsáveis pela criação das suas
respectivas representações.
Figura 12. Classes do protótipo do compilador da linguagem D#. [Fonte:próprio autor]
Como apresentado na seção 4.1 a saída do analisador sintático, representado
pela classe AspectDSharpParser, está no formato de uma AST. Na estrutura base
do compilador, inspirada no padrão de projeto Builder [20], os objetos do tipo Builder
são responsáveis pela interpretação da representação intermediária, produzida pela
classe AspectDSharpParser, e pela construção de uma nova RI (classes com a
terminação Definition) que será utilizada pelo combinador de aspectos, representado
54
pela classe Weaver, na fase de combinação. A Figura 13 exibe os principais
métodos e propriedades da classe Weaver que é responsável por realizar o
processo de combinação entre os aspectos, definidos na propriedade
AspectDefinitions, e o código objeto representado pela propriedade
AssemblyDefinitions.
Figura 13. Classe Weaver. [Fonte: próprio autor]
55
4.5.2 Visão geral do processo de compilação
Figura 14. Visão Geral do Processo de Compilação. [Fonte: adaptado de [32]]
O processo de compilação, apresentado na Figura 14, consiste nas etapas de
análise léxica e sintática (Preprocessing), análise semântica (Analysis & Checking),
geração de código (Code Generation) e combinação (weaving).
No modelo de compilação proposto a entrada do processo é composta por
três tipos de arquivos:
Os arquivos de extensão .ds (Concern files): esses arquivos representam
o código fonte de todos os aspectos da aplicação.
Os arquivos de extensão .cs (Source files): esses arquivos representam o
código fonte OO de classes auxiliares utilizadas pelos aspectos para a
implementação de interesses transversais.
Os arquivos de extensão .dll ou .exe (Bin files): esses arquivos
representam o código compilado da aplicação, sem a implementação dos
interesses transversais. Na plataforma .NET esses arquivos binários estão
do formato ECMA CIL [23] sendo apenas compilados para o código de
máquina no momento da execução.
56
Os arquivos de extensão .cs não são analisados pelo compilador da
linguagem D#. Eles são repassados diretamente para o compilador da linguagem
C#(Base Language Compiler) que efetua o processo de compilação e retorna os
arquivos compilados para o compilador D#. Já os arquivos de extensão .ds são
analisador pelo compilador D# e passam por todas as suas fases de compilação.
Na fase de análise léxica os arquivos de extensão .ds são passados para o
analisador léxico, implementado pela classe AspectDSharpLexer (gerada através da
ferramenta ANTLR), que produz como saída uma sequência de tokens que serão
passados para o analisador sintático.
Na fase de análise sintática a cadeia de tokens do analisador léxico é
passada para o analisador sintático, implementado pela classe AspectDSharpParser
(gerada através da ferramenta ANTLR), que produz como saída uma representação
intermediária do programa em forma de árvore sintática abstrata que servirá como
entrada para a fase de análise semântica.
Na fase de análise semântica a AST produzida pelo analisador sintático é
passada para o analisador semântico, implementado por um conjunto de classes
que executam regras de validações semânticas através do padrão Visitor.
Na fase de geração de código a representação intermediária validada após a
fase de análise semântica, é transformada em objetos das classes AspectDefinition,
PointcutDefinition, AdviceDefinition e JoinpointDefinition. Essas classes são
produzidas pelas classes que implementam o padrão Builder. Após esse processo
de transformação os objetos das classes AspectDefinition e AdviceDefinition sofrem
um processo de tradução, onde a representação intermediária contida nos objetos é
transformada em uma classe do mundo OO escrita na linguagem C#, e logo após é
compilada utilizando-se o compilador padrão da linguagem C#. Dessa forma a saída
produzida pela fase de geração de código consiste, basicamente, na representação
intermediária dos pontos de corte e no código binário produzido através da
compilação do código traduzido dos aspectos. Abaixo segue um pequeno trecho de
código que ilustra, de forma simplificada, o processo de tradução realizado pelo
compilador D#.
57
public void ToString(AspectDefinition aspect,TextWriter textWriter) { textWriter.Write("public class aspect_"); textWriter.WriteLine(aspect.Name); textWriter.WriteLine("{"); foreach (AdviceDefinition adviceDefinition in aspect.Advices) { ToString(adviceDefinition, textWriter); } textWriter.WriteLine("}"); } public void ToString(AdviceDefinition advice,TextWriter textWriter) { textWriter.Write("public static void "); textWriter.Write(advice.AdviceType.Name); textWriter.Write("_"); textWriter.Write(advice.pointcutExpression.Name); textWriter.Write("("); foreach (string token in advice.AdviceParameters.Content) { textWriter.Write(token); } textWriter.WriteLine(")"); foreach (string token in advice.AdviceBody.Content) { textWriter.Write(token); } }
Na fase de combinação o código compilado dos aspectos, gerado na fase de
geração de código, é entrelaçado ao código de componentes da aplicação (Bin
Files), que está dentro de um Assembly no formato de linguagem intermediária,
através da classe Weaver que utiliza a representação intermediária dos pontos de
cortes (PointcutDefinition) e a biblioteca mono Cecil [22] para realizar a inserção dos
códigos dos adendos e das declarações inter-tipos, com o objetivo de gerar o código
executável contendo as implementações dos interesses transversais. Cecil é uma
biblioteca usada para gerar e inspecionar programas e bibliotecas escritas no
formato ECMA CIL, com ela é possível carregar e modificar Assemblies na memória
e depois salvá-los novamente em disco.
4.5.3 Um exemplo de aplicação
Nesta etapa é demonstrado o uso do protótipo do compilador em uma
aplicação que simula um cenário clássico da programação orientada a objetos. Este
cenário consiste em uma simplificação do padrão de projeto fachada aplicado ao
58
gerenciamento de registros em uma aplicação comercial. O cenário proposto
consiste de uma classe denominada Facade que realiza as operações de inserção,
criação, atualização e exclusão sobre objetos da classe Record. Deseja-se, então,
adicionar a funcionalidade de log de operações, um interesse transversal, em todas
as chamadas aos métodos da fachada. Para tanto, será implementado o aspecto
AspectLogger que realizará o registro de todas as chamadas a métodos da classe
fachada. A trecho de código abaixo exibe o código fonte das classes da aplicação e
do aspecto AspectLogger.
public class Record { public string ID { get; set; } public object Data { get; set; } public Record() { } public Record(string id, object data) { this.ID = id; this.Data = data; } } public class Facade { private List<Record> repository = new List<Record>(); public Record Create(string id,object data) { Console.WriteLine("Executing Create..."); Record r = new Record(); r.ID = id; r.Data = data; repository.Add(r); return r; } public Record Retrieve(string id) { Console.WriteLine("Executing Retrieve..."); return repository.Single(r => r.ID == id); } public void Update(Record record) { Console.WriteLine("Executing Update..."); repository.Single(r => r.ID == record.ID).Data = record.Data; } public void Delete(Record record) { Console.WriteLine("Executing Delete..."); repository.Remove(record); } } public class Logger { public static void LogMessage(string message) { Console.WriteLine("Message:" + message); } }
59
class Program { static void Main(string[] args) { Facade facade = new Facade(); facade.Create("id", "data"); Record r = facade.Retrieve("id"); Record r1 = new Record("id", "data1"); facade.Update(r1); facade.Delete(r); System.Console.Read(); } } public issingleton aspect AspectLogger{ public pointcut FacadeCalls(): call(*.Facade.*(...)); before() : FacadeCalls { Logger.LogMessage("before FacadeCall"); } after() : FacadeCalls { Logger.LogMessage("after FacadeCall"); } }
Apesar da simplicidade da aplicação apresentada, observamos que ela pode
ser facilmente estendida para cenários complexos, como os citados no capítulo 2,
onde a POA torna-se essencial para um bom design da aplicação. A Figura 15 exibe
o código fonte combinado (lado direito da figura) gerado pelo protótipo do compilador
criado, onde podemos observar a inserção das chamadas ao método
after_FacadeCalls, que representa o adendo after, e before_FacadeCalls, que
representa o adendo before. A ferramenta .NET Reflector é um Diassembler capaz
de gerar, a partir dos arquivos binários, o código fonte C# correspondente ao código
compilado no formato ECMA CIL.
61
Capítulo 5
Conclusão e Trabalhos Futuros
Atualmente, a grande demanda por software cada vez mais complexos exige
a criação de novos métodos, técnicas e ferramentas que auxiliem o processo de
desenvolvimento de software. Na Programação Orientada a Aspectos é essencial o
uso de ferramentas que auxiliem os processos de escrita, refatoração e depuração
de programas. Entretanto, atualmente, na plataforma .NET é escassa a existência de
ferramentas que dêem um suporte completo aos conceitos propostos pela POA e
diante da dificuldade envolvida em projetar sistemas dessa natureza, este trabalho
iniciou o projeto de uma solução (linguagem e compilador), de código aberto,
visando minimizar o problema da escassez desse tipo de ferramentas na plataforma
.NET. O produto final deste trabalho, a linguagem, o front end e um protótipo do
back end do compilador, ainda não pode ser considerado pronto para aplicação, pois
o back end produzido implementa um conjunto restrito das funcionalidades
suportadas pela linguagem D#. No momento constitui um protótipo que pode ser
estendido e utilizado como ponto de partida para uma implementação totalmente
funcional de um compilador para a linguagem proposta.
5.1 Discussão dos resultados e contribuições
Uma extensão orientada a aspectos da linguagem de programação C# foi
proposta com o objetivo de resolver o problema da escassez de ferramentas que
dêem suporte completo a POA na plataforma.
Neste trabalho foi produzida uma gramática da linguagem D# a partir da qual
foram gerados os analisadores léxico e sintático que são capazes de reconhecer
todas as construções propostas pela linguagem D#, embora, também, reconheçam,
algumas construções não válidas para linguagem proposta. Para a fase de análise
semântica foi proposta uma estrutura, baseada no padrão de projeto Visitor, que é
capaz de validar um programa escrito na linguagem D# através de caminhamentos
62
na AST produzida na análise sintática. Atualmente foram implementadas apenas
duas regras de análise semântica com o objetivo de validar a estrutura de análise
proposta neste trabalho. Para a fase de geração de código foi proposta uma
abordagem baseada em um processo de tradução que transforma o código fonte
dos aspectos em uma classe escrita na linguagem C#. Atualmente o gerador de
código dá suporte a geração de adendos do tipo after e before e declarações inter-
tipos de membros que inserem atributos e propriedades. Para a fase de combinação
foi proposto o uso da ferramenta mono Cecil para entrelaçar, em nível da linguagem
intermediária, os códigos dos aspectos e dos componentes e ,atualmente, o Weaver
da suporte apenas aos pontos de corte do tipo call e execution.
Partindo do pressuposto que a criação de ferramentas é de extrema
importância para a popularização do paradigma orientado a aspectos, podemos
considerar que a construção da extensão da linguagem C# para atender tal
paradigma, produz um avanço rumo à popularização da técnica, haja vista a
escassez de linguagens e ferramentas que auxiliem o desenvolvimento de sistemas
orientados a aspectos na plataforma .NET. No entanto, os resultados alcançados
neste trabalho, por si só, não produzem os resultados necessários para uma real
utilização da linguagem. Sendo, assim, o principal fruto desse trabalho a
consolidação dos caminhos a serem trilhados na construção da solução final,
solução esta que será composta pela linguagem e seu compilador e por ferramentas
que venham facilitar o processo de desenvolvimento de sistemas OA na plataforma
.NET.
5.2 Trabalhos Futuros
Esta monografia criou um ponto de partida para a implementação funcional de
uma extensão orientada a aspectos da linguagem de programação C#. Espera-se
que trabalhos futuros venham aperfeiçoar e estender a ferramenta proposta neste
trabalho. Com esse objetivo foram listadas abaixo as principais melhorias e pontos
de extensão do trabalho proposto:
63
Análise sintática: Nessa fase a principal melhoria é a redefinição de
algumas produções da gramática desenvolvida e da gramática utilizada
como ponto de partida na implementação, com o intuito de impedir o
reconhecimento de algumas entradas não válidas na linguagem D#. Um
exemplo é a produção modifiers que é usada para definir modificadores de
acesso e herança em aspectos, pontos de corte e adendos. Na gramática
proposta a produção reconhece como válida uma entrada do tipo
“abstract public protected” que não é valida na linguagem D#.
Análise semântica: Nessa fase o principal ponto de extensão é a adição de
novas regras semânticas com o intuito de validar construções sintáticas
que não podem ser validadas pelo analisador sintático gerado a partir da
gramática livre de contexto produzida.
Geração de código: Nessa fase o principal ponto de extensão é o suporte
a geração de código para os adendos do tipo around e instead e para as
declarações inter-tipos de membros que inserem métodos. Além do
suporte as declarações inter-tipos de herança, precedência e de erros e
avisos.
Combinação: Nessa fase o principal ponto de extensão é o suporte aos
pontos de corte do tipo get, set, fget, fset, adviceexecution, within, handler,
withincode, this, target, args, cflow, cflowbelow, destructorexecution,
staticinitialization, preinitialization.
Além das extensões e melhorias propostas outros trabalhos relacionados
podem ser realizados:
Combinação em tempo de execução: Um modelo de entrelaçamento
em tempo de execução pode ser proposto para a linguagem D#. O modelo
de combinação em tempo de compilação adotado pela linguagem D#
possui um melhor desempenho na execução, porém não fornece suporte
ao entrelaçamento de tipos criados dinamicamente.
Análise comparativa da performance do código gerado: Um estudo
pode ser realizado com o intuito de comparar a performance do código
65
Bibliografia
[1]. Kiczales, Gregor et al. Aspect-Oriented Programming. European
Conference on Object-Oriented Programming, ECOOP. Alemanha, Junho,1997. p.
220-243
[2]. Soares, Sérgio. C. B. Programação Orientada a Aspectos. Será que vai
Pegar? [Online] [Citado em: 9 de 10 de 2010.]
http://www.cin.ufpe.br/~scbs/aspectos/Palestra2.pdf.
[3]. Eaddy,Marc et. al. . The Value of Improving the Separation of Concerns.
[Online] [Citado em: 9 de 10 de 2010.]
http://www.cs.columbia.edu/~eaddy/publications/TheValueOfImprovingTheSeparatio
nOfConcerns.pdf.
[4]. Laddad, Ramnivas. AspectJ in Action: Enterprise AOP with spring
applications. 2. s.l. : Manning Publications Co, 2003. p. 512.
[5]. Kersten, Mik. AO tools: State of the (AspectJ) art and open problems.
Workshop on Tools for Aspect-Oriented Software Development - OOPSLA. 11 de
2002.
[6]. Lam, Hoang e Thai, Thuan. Net Framework Essentials. 1. s.l. : O'Reilly
Media, 2001. p. 320.
[7]. Watt, David. Programming Language Concepts and Paradigms. Prentice
Hall, 1990, p. 322
[8]. AOSD.NET: Tools for Developer. [Online] [Citado em: 9 de 10 de 2010.]
http://www.aosd.net/wiki/index.php?title=Tools_for_Developers.
[9]. Aho, Alfred, et al. Compiladores: Princípios, técnicas e ferramentas. 2.
São Paulo : Pearson Addison-Wesley, 2008. p. 635.
[10]. Meyer, Bertrand. Object-Oriented Software Construction. Segunda
Edicao. Prentice Hall, 1988,p. 1296.
66
[11]. Jacobson, Ivar. Object Oriented Software Engineering: A Use Case
Driven Approach. Primeira Edição. Addison-Wesley Professional, 1992, p. 524
[12]. Edsger W. Dijkstra. A Discipline of Programming. s.l. : Prentice Hall, Inc,
1976. p. 217.
[13]. Ossher, H. e Tarr, P. Using subject-oriented programming to overcome
common problems in object-oriented software development/evolution. International
Conference on Software Engineering - ICSE. 1999, pp. 688-698.
[14]. Lieberherr, K. J., Silva-Lepe, I. e Xiao, C. Adaptive Object- Oriented
Programming Using Graph-Based Customization. Communications of the ACM.
1994, pp. 94-101.
[15]. Grune, Dick, et al. Modern Compiler Design. 1. Amsterdã : Wiley, 2000.
p. 754.
[16]. Parr, Terence. The Definitive ANTLR Reference: Building Domain-
Specific Languages. s.l. : The Pragmatic Programmers, 2007. p. 369.
[17]. Parr, Terence. Language Implementation Patterns: Create Your Own
Domain-Specific and General Programming Languages. 1. s.l. : The Pragmatic
Programmers, 2009. p. 389.
[18]. Codeplex. ANTLR C# grammar . codeplex. [Online] [Citado em: 09 de 10
de 2010.] http://antlrcsharp.codeplex.com/.
[19]. Chomsky, N. Three Models for the description of language. IRE Trans.
on Information Theory. IT-2-3, 1973, p. 113-124 .
[20]. Gamma ,Erich;Helm, Richard;Johnson, Ralph;Vlissides, John M.
Design Patterns: Elements of Reusable Object-Oriented Software. 1. s.l. : Addison-
Wesley Professional, 1994. p. 416 .
[21]. Cohen, Tal e Gil, Joseph. Towards an Aspect Based, Programmable,
and Extensible Middleware Framework. European Conference on Object-Oriented
Programming - ECOOP 2004. 18, 2004.
[22]. Evain,Jean-Baptiste. Mono Project. Cecil. [Online] [Citado em: 10 de 11
de 2010.] http://www.mono-project.com/Cecil.
67
[23]. ECMA INTERNATIONAL. ECMA CIL. [Online] Ecma International.
[Citado em: 10 de 10 de 2010.] http://www.ecma-
international.org/publications/standards/Ecma-335.htm.
[24] Andreloker .Net Vision Blog. [Citado em: 18 de 12 de 2010.]
http://blog.andreloker.de/post/2009/02/13/Simple-AOP-introduction-to-AOP.aspx.
[25] Soares, Sérgio. C. B. Desenvolvimento de Software Orientado a
Aspectos [Online] [Citado em: 9 de 10 de 2010.]
http://www.cin.ufpe.br/~scbs/aspectos/Palestra1.pdf
[26] Characterization Of Existing Approaches [Online] [Citado em: 18 de 12 de
2010.]http://janus.cs.utwente.nl:8000/twiki/bin/view/AOSDNET/CharacterizationOfExi
stingApproaches
[27] PostSharp: code to the point [Online] [Citado em: 18 de 12 de 2010.]
http://www.sharpcrafters.com
[28] Schult, Wolfgang; Polze, Andreas. Aspect-Oriented Programming with
C# and .NET. Proceedings of the Fifth IEEE International Symposium on Object-
Oriented Real-Time Distributed Computing, ISORC-02, Washington,2002, p. 241-248
[29] C# grammar for SableCC. [Online] [Citado em: 20 de 12 de 2010.]
http://www.sable.mcgill.ca/listarchives/sablecc-list/msg00981.html
[30] C# gramar for Coco/R. [Online] [Citado em: 20 de 12 de 2010.]
http://www.ssw.uni-linz.ac.at/Research/Projects/Coco/CS/CSharp2.ATG
[31] Comparison of parser generators. [Online] [Citado em: 20 de 12 de 2010.]
http://en.wikipedia.org/wiki/Comparison_of_parser_generators
[32] Compose star documentation. [Online] [Citado em: 20 de 12 de 2010.]
http://janus.cs.utwente.nl:8000/twiki/pub/Composer/InternalPresentations/Composest
arDaySection2.ppt
68
Apêndice A
Gramática da linguagem D#
Observações:
Por questões de tamanho a maioria das produções da linguagem C# foram omitas (uma lista completa das produções da linguagem C# pode ser encontrada em [18].
namespace_member_declarations:
namespace_member_declaration+;
namespace_member_declaration:
attributes? modifiers? type_declaration;
type_declaration:
('partial') => 'partial' (class_declaration
| struct_declaration
| interface_declaration)
| class_declaration
| struct_declaration
| interface_declaration
| enum_declaration
| delegate_declaration
| aspect_declaration;
aspect_declaration:
perclause? 'aspect' type_or_generic class_base? type_parameter_constraints_clauses? aspect_body;
perclause:
'issingleton'
| ('perthis' | 'pertarget' | 'percflow' | 'percflowbelow' ) '(' member_name ')' ;
aspect_body:
'{' aspect_member_declarations? '}';
aspect_member_declarations:
aspect_member_declaration+;
aspect_member_declaration:
69
modifiers?
( pointcut_declaration | (type? advice_declaration) | intertype_declaration);
pointcut_declaration:
'pointcut' pointcut_header ';';
pointcut_header:
type_or_generic '(' formal_parameter_list? ')' type_parameter_constraints_clauses? (':' pointcut_expr)?
pointcut_expr :
or_pointcut_expr ( '&&' or_pointcut_expr)*;
or_pointcut_expr :
unary_pointcut_expr ('||' unary_pointcut_expr)*;
unary_pointcut_expr :
basic_pointcut_expr
| '!' unary_pointcut_expr;
basic_pointcut_expr:
'(' pointcut_expr ')'
| member_name
| 'adviceexecution()'
| 'destructorexecution()'
| 'initialization' '(' method_constructor_pattern ')'
| 'preinitialization' '(' method_constructor_pattern ')'
| 'call' '(' method_constructor_pattern ')'
| 'execution' '(' method_constructor_pattern ')'
| 'withincode' '(' method_constructor_pattern ')'
| 'get' '(' pointcut_qualified_identifier ')'
| 'set' '(' pointcut_qualified_identifier ')'
| 'fget' '(' pointcut_qualified_identifier ')'
| 'fset' '(' pointcut_qualified_identifier ')'
| 'handler' '(' pointcut_qualified_identifier ')'
| 'staticinitialization' '(' pointcut_qualified_identifier ')'
| 'within' '(' pointcut_qualified_identifier ')'
| 'cflow' '(' member_name ')'
| 'cflowbelow' '(' member_name ')'
| 'this' '(' qualified_identifier ')'
| 'target' '(' qualified_identifier ')'
| 'args' '(' pointcut_formal_parameter_list ')' ;
advice_declaration:
advice_type '(' formal_parameter_list? ')' advice_break_type?
70
( '(' advice_break_type_parameter ')' )? ':' pointcut_expr advice_body;
advice_type :
'before'|'after'|'around'|'instead';
advice_break_type:
'returning' | 'throwing';
advice_break_type_parameter:
type identifier;
advice_body: block;
intertype_declaration:
'insert into' ':' pointcut_qualified_identifier '{' intertype_member_declarations '}'
'declare parent' ':' pointcut_qualified_identifier ':' type_or_generic ';'
|'declare interfaces' ':' pointcut_qualified_identifier ':' class_base ';'
|'declare precedence' ':' class_base ';'
|'declare error' ':' pointcut_expr ':' STRINGLITERAL ';'
|'declare warnig' ':' pointcut_expr ':' STRINGLITERAL ';';
intertype_member_declarations:
intertype_member_declaration+;
intertype_member_declaration:
attributes?
modifiers?
(
'const' type constant_declarators';'
| 'void' method_declaration
| type ( (member_name '(') => method_declaration
| (member_name '{') => property_declaration
| (member_name '.' 'this') => type_name '.' indexer_declaration
| indexer_declaration
| field_declaration
| operator_declaration
)
| delegate_declaration
| conversion_operator_declaration
| constructor_declaration
| destructor_declaration
);
method_constructor_pattern:
method_pattern;
method_pattern :
71
pointcut_qualified_identifier '(' pointcut_formal_parameter_list ')';
pointcut_qualified_identifier:
type_or_wildcard ('.' type_or_wildcard)* ;
type_or_wildcard:
identifier '+'? | wildcard ;
wildcard:
'*';
pointcut_formal_parameter_list:
'__arglist'
| pointcut_formal_parameter (',' pointcut_formal_parameter)*
| '...' ;
pointcut_formal_parameter:
parameter_modifier? 'params'? type
| '*' ;
type_or_generic:
(identifier '<') => identifier generic_argument_list
| identifier;
identifier:
IDENTIFIER;
IDENTIFIER:
IdentifierStart IdentifierPart* ;
IdentifierStart: '@' | '_' | 'A'..'Z' | 'a'..'z' ;
IdentifierPart: 'A'..'Z' | 'a'..'z' | '0'..'9' | '_' ;
generic_argument_list:
'<' type_arguments '>' ;
type_arguments:
type (',' type)* ;
type:
((predefined_type | type_name) rank_specifiers) => (predefined_type | type_name) rank_specifiers '*'*
| ((predefined_type | type_name) ('*'+ | '?')) => (predefined_type | type_name) ('*'+ | '?')
| (predefined_type | type_name)
| 'void' '*'+;
predefined_type:
'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int' | 'long' | 'object' | 'sbyte'
| 'short' | 'string' | 'uint' | 'ulong' | 'ushort' ;
type_name:
namespace_or_type_name ;
72
namespace_or_type_name:
type_or_generic ('::' type_or_generic)? ('.' type_or_generic)* ;
class_base:
':' interface_type_list;
interface_type_list:
type (',' type)*
type_parameter_constraints_clauses:
type_parameter_constraints_clause+
type_parameter_constraints_clause:
'where' type_variable_name ':' type_parameter_constraint_list ;
type_parameter_constraint_list:
('class' | 'struct') (',' secondary_constraint_list)? (',' constructor_constraint)?
| secondary_constraint_list (',' constructor_constraint)?
| constructor_constraint ;
secondary_constraint_list:
secondary_constraint (',' secondary_constraint)* ;
secondary_constraint:
type_name ;
type_variable_name:
identifier ;
constructor_constraint:
'new' '(' ')' ;