![]() |
Trabalho Prático 2
Processamento de Linguagens
|
O presente software foi desenvolvido no contexto académico e deve ser utilizado sem qualquer garantia por conta e risco do utilizador.
Autores:
Links Uteis
O objetivo deste trabalho é o de implementar uma ferramenta capaz de ler um documento de texto usando uma determinada sintaxe. A resolução deste trabalho foi dividida em duas partes :
De forma a comprovar os conceitos de analisador léxico e sintático, foi-nos proposta uma série de problemas e de entre essas propostas dadas, deveríamos escolher uma para implementar. A nossa escolha recaiu no problema 2: Linguagem de Desenho de Imagens Raster por nos parecer um enunciado bastante interessante do ponto de vista de comprovar as valências do uso do "par Flex-Bison".
De forma bastante sucinta, este problema propunha a implementação de uma linguagem para a descrição e criação de imagens através da interpretação de um simples documento de texto com determinada sintaxe pré-estabelecida.
Para a realização deste trabalho foram utilizadas as ferramentas abaixo descritas:
Para resolver tal problema foi-nos sugerido o uso do formato PNM por ser um formato bastante simples a nível de leitura e escrita e com a vantagem de poder ser armazenado em memória usando matrizes. A família PNM possui vários sub-formatos sendo designados por P*n* estando n definido entre 1 e 6. As diferenças entre eles baseiam-se na forma como são codificados (ASCII ou Binário) e quais os conjuntos de cores que conseguem armazenar (B&W, 256 cinzentos ou 16M cores).
| Nome | P1 | P2 | P3 | P4 | P5 | P6 |
|---|---|---|---|---|---|---|
| Cores | B&W | 256 Cinzentos | 16M Cores | B&W | 256 Cinzentos | 16M Cores |
| Codificação | ASCII | ASCII | ASCII | Binário | Binário | Binário |
Face à representação vista na tabela acima, resolvemos utilizar o formato P6 pois o mesmo permite a criação de imagens a 16M Cores, mesmo que a nível de codificação, os pixeis da imagem sejam em binário, usando um byte para representar cada cor.
A linguagem de desenho é definida através da interpretação de um conjunto de comandos previamente estabelecidos e devem terminal por ponto e virgula (;). Definiu-se ainda que:
Para a gestão das imagens foram definidas os seguintes comandos:
Segundo o enunciado, todas as primitivas de desenho deverão aceitar um último parâmetro com a cor a ser usada para desenhar. No entanto pode ser definida uma cor "por omissão", sendo que essa será definida pelo comando COLOR.
Aceita ainda como parâmetros o ponto que se encontra na posição do canto superior esquerdo e as dimensões do quadrado.
Como se pode verificar, muitos dos parâmetros que na secção anterior foram apresentados são definições de pontos geométricos e códigos RGB para as cores a aplicar na instrução. Face a esse facto, podemos então definir variáveis que permitirão armazenar os dados referentes aos pontos geométricos ou ao código RGB da cor.
A sua atribuição é similar ao utilizado na linguagem C, utilizando-se o caráter '='. Tal como em C, a atribuição de dados a uma variável pode ser do tipo numérico ou outra variável.
Salienta-se o facto que os caracteres "x" "y" "z" não podem ser usados como variáveis pois no contexto do problema serão usados como delineadores de instruções.
Uma outra funcionalidade bastante importante na geração imagens é o facto de podermos criar imagens com pontos aleatórios. Para tal, foi necessário utilizar a função RAND nativa do C para gerar pontos aleatórios na área de desenho, sendo que o valor apresentado após o termo RAND é o valor máximo admitido.
No exemplo acima apresentado, a variável a pode tomar valores entre 0 e 10, inclusive.
É também muito importante que a linguagem de desenho aceite iterar várias vezes a mesma instrução. Para tal, é necessário aplicar o conceito de ciclos. Foi desenvolvido então um ciclo for muito similar ao aplicado em linguagens imperativas, no entanto com ligeiras nuances.
Para compilar as aplicações necessárias simplesmente é necessário efectuar o comando:
O executavel será criado na pasta bin e pode ser executado da seguinte forma:
O Flex é um software desenvolvido com o intuito de permitir a análise léxica de um determinado conjunto de dados através do reconhecimento de sequências de caracteres ("tokens") definidas pelo programador.
Um ficheiro de Flex é composto por três zonas delimitadas por %%
A primeira parte, o preâmbulo, é o local onde se faz a inclusão das bibliotecas e da definição de conjuntos de caracteres.
No ficheiro por nós desenvolvido, o preâmbulo é constituído pela inclusão da biblioteca com a definição das funções necessárias para a criação de um software de desenho de imagens em Raster. É também definida no preâmbulo o ficheiro grammar.tab.h que informa ao analisador léxico que o Flex vai ser usado em conjunto com o Bison.
A segunda parte, o corpo, é a zona na qual estão definidas as expressões regulares, assim como as ações semânticas que poderão ser despoletadas aquando do eventual reconhecimento de um token.
Na nossa implementação, o corpo é constituído por expressões regulares que permitem reconhecer em Ignore Case Sensitive as diversas instruções e caracteres especiais utilizados pela nossa implementação de desenho de imagens em Rasper. Se for reconhecido algum dos tokens definidos no lexer.l, é retornado para o programa a devida função de C a ser invocada para tratar aquele comando.
A terceira parte é destinada para as funções adicionais necessárias para o bom funcionamento do analisador léxico.
Nesta implementação, foi definida a função yywrap() que retorna 1 e que passa a informação ao analisador léxico que estamos na presença de EOF (End Of File).
O Bison é um software desenvolvido com o intuito de permitir gerar automaticamente programas para análise sintática. Possui como entrada a declaração de uma gramática, que especifica uma linguagem e gera como saída o parser dessa linguagem. O parser gerado pelo Bison permite validar os argumentos lá introduzidos permitindo assim, averiguar se os mesmos seguem a lógica sintática definida na nossa gramática.
De forma similar ao que acontece na estrutura de ficheiro do Flex, um ficheiro de Bison é também composto por três zonas delimitadas por %%
A primeira parte, o preâmbulo, é o local onde se faz a inclusão das bibliotecas a serem utilizadas.
No ficheiro por nós desenvolvido, o preâmbulo é constituído pela inclusão da biblioteca com a definição das funções necessárias para a criação de um software de desenho de imagens em Raster. É também incluído no preâmbulo a biblioteca commands.h que informa ao analisador sintático que as funções que concretizam as instruções definidas na gramática estão concretizadas nessa biblioteca.
São ainda definidos outros blocos de código que dizem respeito a outras declarações.
Na nossa implementação foram necessárias usar algumas definições extra que têm como objetivo o seguinte:
A segunda parte, o corpo, é a zona na qual estão definidas as produções e o respetivo código a ser invocado caso a regra sintática seja correspondida.
Na nossa implementação, o corpo é constituído pelas produções que permitem verificar se a sintaxe usada no ficheiro de texto permite criar a imagem. Em caso afirmativo são invocadas as funções necessárias para a criação da imagem. Em caso contrário a imagem não será criada.
A terceira parte é destinada para as funções adicionais necessárias para o bom funcionamento do analisador sintático.
Nesta implementação, foi definida a função yyerror() que retorna o código do erro para o utilizador, caso algum erro se verifique. Caso contrário retorna um 0.
Após a concretização de todas as funções e comandos necessários para a resolução do problema, se for executado o comando abaixo descrito obteremos a imagem PNM (P6) que esperávamos.
Seguem abaixo alguns exemplos gerados:

A título de conclusão, podemos verificar que um compilador é um componente bastante complexo, na medida em todo o progresso de análise léxica e sintática regem-se por regras bastante bem definidas.
Concluímos ainda que a junção Flex-Bison permite simular muito bem qual o WorkFlow que o compilador utiliza de cada vez que necessita de verificar os dados que a ele são submetidos.
Verificámos ainda que o processo de análise sintática é muito mais complexa do que a análise léxica e que a definição de uma gramática deve ser o mais objetiva possível de forma a impedir situações de erros derivados a ambiguidades do analisador sintático.