UMA EXTENSÃO ORIENTADA A ASPECTOS PARA C#tcc.ecomp.poli.br/20102/monografia-daa.pdf · Entretanto...

81
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

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.

i

Dedicatória

Aos meus pais

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

3

POO – Programação Orientada a Objetos

RI – Representação Intermediária

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.

60

Figura 15. Visualização do código combinado na ferramenta .NET Reflector. [Fonte:

próprio autor]

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

64

gerado pelo compilador D# em relação a outras ferramentas OA e às

abordagens OO.

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' '(' ')' ;