Você está na página 1de 60

Faculdade de Tecnologia de So Paulo - FATEC-SP Departamento de Tecnologia da Informao Curso de Processamento de Dados

Implementao de um Compilador que Gera uma Representao Grca do Programa Compilado

Bruno de Brito Coimbra

Faculdade de Tecnologia de So Paulo - FATEC-SP Departamento de Tecnologia da Informao Curso de Processamento de Dados

Implementao de um Compilador que Gera uma Representao Grca do Programa Compilado

Bruno de Brito Coimbra

Monograa submetida como exigncia parcial para a obteno do Grau de Tecnlogo em Processamento de Dados Orientador: Prof. Dr. Silvio do Lago Pereira

So Paulo 2011

Wyrd bi ful ard

Sumrio
1 Introduo 2 Referencial Terico 3 Implementao 4 Concluso Bibliograa A Listagem dos cdigos-fonte 1 2 17 27 32 34

Lista de Figuras
1 2 3 4 5 6 rvores Sintticas Ambguas . . . . . . . . . . . . . . . . . . . . . . . . Tabela de Hashes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exemplo Grafo Gerado pelo dot . . . . . . . . . . . . . . . . . . . . . . Mdulos do Compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 13 16 19 28 31

Representao Grca Atual do Programa Fibonacci

Melhoria da Representao Grca do Programa Fibonacci . . . . . . .

Listagens
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Exemplo de Gramtica Ambgua . . . . . . . . . . . . . . . . . . . . . 7 9 9 10 11 14 15 16 17 17 21 21 24 24 25 25 27 29 34 35 35 36 39 39 41 42 43 44 46 47 Gramtica Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Programa Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Formato Especicao YACC . . . . . . . . . . . . . . . . . . . . . . .

Exemplo de especicao YACC . . . . . . . . . . . . . . . . . . . . . . Cdigo de 3 Endereos . . . . . . . . . . . . . . . . . . . . . . . . . . . Exemplo Gerador de Cdigo . . . . . . . . . . . . . . . . . . . . . . . . Exemplo de Grafo Expresso em DOT . . . . . . . . . . . . . . . . . . . Exemplo de Clculo da Sequncia de Fibonacci . . . . . . . . . . . . .

Gramtica reconhecida . . . . . . . . . . . . . . . . . . . . . . . . . . . Instruo de Escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Instruo de Atribuio . . . . . . . . . . . . . . . . . . . . . . . . . . . Funo geradora do comando while em C . . . . . . . . . . . . . . . . . Programa Fibonacci Compilado em C . . . . . . . . . . . . . . . . . . . Programa Fatorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Programa Fatorial Compilado em C . . . . . . . . . . . . . . . . . . . . Grafo DOT Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . .

Melhoria Grafo DOT do Programa Fibonacci . . . . . . . . . . . . . . . compiler.c global.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

scanner.l . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . parser.y . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

util.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . util.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . symtab.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . symtab.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . cgen.h cgen.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

dotgen.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . dotgen.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Lista de Acrnimos
ASCII BNF RI LALR SLR
American Standard Code for Information Interchange Backus-Naur Form Look-Ahead LR

Representao Intermediria Simple LR Yet Another Compiler Compiler

YACC

Resumo

Este trabalho tem como principal objetivo descrever a implementao de um compilador capaz de gerar uma representao grca da lgica do programa. Mais precisamente, este compilador gera duas representaes, semanticamente equivalentes, do programa-fonte: uma em linguagem C, que pode ser compilada por um compilador C padro, e outra em linguagem DOT que, ao ser compilada, gera uma representao grca da lgica do programa. Espera-se que esta representao grca seja uma ferramenta que facilite a aprendizagem de programao, uma vez que ela torna explcito uxo de execuo do programa para os novatos nesta rea.

Palavras-chave: compiladores, Linguagem C, DOT, Lex, Yacc

Abstract

This work aims as main objective to describe the implementation of a compiler that can generate a graphical representation of program logic. Precisely, this compiler generates two program-objects: one in C language that can be compiled with a standard C compiler and another in DOT language when compiled build a graphical representation of program logic. Is hoped this graphical representation could be a tool that help people to learn programming, because it shows, explicitly, the execution program ow for novices in this knowledge branch.

Key words: compilers, C language, DOT, Lex, YACC

1 Introduo
Este trabalho tem como principal objetivo descrever a implementao de um compilador capaz de gerar uma representao grca da lgica do programa. Mais precisamente, este compilador gera duas representaes, semanticamente equivalentes, do programa-fonte: uma em linguagem C, que pode ser compilada por um compilador C padro, e outra em linguagem DOT que, ao ser compilada, gera uma representao grca da lgica do programa. Espera-se que esta representao grca seja uma

ferramenta que facilite a aprendizagem de programao, uma vez que ela torna explcito uxo de execuo do programa para os novatos nesta rea. Sero apresentadas tcnicas e exemplos de anlise lxica e sinttica, gerao de cdigo objeto e algumas estruturas de dados necessrias para a implementao. Tambm sero demonstrados neste trabalho a utilizao de ferramentas de auxlio ao desenvolvimento de compiladores, como geradores de analisadores lxicos e sintticos. O trabalho foi estruturado de forma que o leitor faa uma leitura linear, sem que haja saltos entre as sees. Na primeira seo deste trabalho (Referencial Terico) so apresentados, de forma sucinta, os conceitos fundamentais sobre os tema. Na seo seguinte (Implementao) discutida a implementao do compilador propriamente dito, quais foram as tcnicas, ferramentas, algoritmos e estruturas de dados utilizadas. A ltima seo (Concluso) apresenta os resultados obtidos, bem como as limitaes do projeto e sugestes de melhoria. No Apndice encontra-se a listagem completa dos programas-fonte. O projeto completo tambm pode ser encontrado em

http://github.com/bbcoimbra/

compiler,

mesmo local em que sero includas as correes e melhorias.

2 Referencial Terico
2.1 Compiladores
Segundo Aho et al. (2008), compilador um programa que traduz um programa-fonte para um programa-objeto. Se o programa-objeto for executvel, en-

to ele estar num formato que um computador possa execut-lo. Dessa forma, um compilador recebe como entrada um arquivo contendo um programa escrito em uma linguagem previamente determinada (programa-fonte) e produz como sada um programa objeto semanticamente equivalente ao programa recebido como entrada. Dessa forma, podemos dizer que um compilador , tambm, um tradutor. Adicionalmente o compilador tem como tarefa reportar os erros encontrados durante o processo de traduo. O compilador pode ser dividido em alguns mdulos para efetuar o processo de traduo. A lista abaixo foi proposta por Louden (2004):

Analisador Lxico; Analisador Sinttico; Analisador Semntico; Gerador de Cdigo.

O Analisador Lxico o responsvel por agrupar os caracteres, contidos no arquivo do programa-fonte, em unidades signicativas, chamadas tokens, e encaminhlas para o Analisador Sinttico. Por sua vez, o Analisador Sinttico verica se o uxo de tokens recebidos pelo Analisador Lxico vlido para a gramtica (ou linguagem) que foi denida. Usualmente o Analisador Sinttico produz uma estrutura (tipicamente uma do tipo rvore) que representa o programa fonte (AHO et al., 2008). O Analisador Semntico recebe a estrutura produzida pelo Analisador Sinttico e, principalmente, verica se as operaes so coerentes para os tipos de dados utilizados e faz a inferncia dos tipos de dados. Ao nal do processo temos uma r-

vore Anotada. As notas (informaes de inferncia, por exemplo) podem ser includas
diretamente na estrutura recebida do Analisador Sinttico, ou numa estrutura auxiliar como uma Tabela de Smbolos.

Estando a rvore Anotada disponvel para o Gerador de Cdigo, este executa a traduo das estruturas recebidas para o programa-objeto. Nessa fase da compilao podem ser includas otimizaes para que o programa traduzido execute de forma mais eciente. Podem haver outras fases intermedirias durante o processo, como, por exemplo, as fases de Otimizao de Cdigo, dependentes ou no da mquina-alvo. Nas prximas Sees, discutiremos mais profundamente cada uma dessas etapas do processo de compilao.

2.2

Anlise Lxica

O processo de Anlise Lxica consiste em agrupar os caracteres do arquivo de entrada em unidades numa estrutura chamada token. Um token tambm chamado de lexema. Segundo Ferreira (1986), lexema o elemento que encerra o signicado da palavra. Ou seja, o menor conjunto de caracteres representativos para uma gramtica de uma linguagem. Dessa forma, o Analisador Lxico remove a responsabilidade de

representar os tokens do Analisador Sinttico, simplicando sua implementao. Segundo Aho et al. (2008), os tokens so denidos como segue:

Um token consiste em dois componentes, um nome de token e um valor de atributo. Os nomes de token so smbolos abstratos usados pelo analisador para fazer o reconhecimento sinttico. Frequentemente, chamamos esses nomes de token de terminais, uma vez que eles aparecem como smbolos terminais na gramtica para uma linguagem de programao. O valor do atributo, se houver, um apontador para a tabela de smbolos que contm informaes adicionais sobre o token. (. . . ).
Ainda segundo Aho et al. (2008), o Analisador Lxico possui algumas atribuies adicionais, como por exemplo, remover espaos em branco e comentrios, efetuar contagem de linhas correlacionando um erro com o nmero da linha em que este foi encontrado. Tipicamente, o Analisador Lxico no gera o todo o uxo de tokens de uma vez. Em vez disso, a demanda de anlise dos tokens ca sob a responsabilidade do

Analisador Sinttico, que recebe os tokens ativando uma funo disponibilizada pelo Analisador Lxico (LOUDEN, 2004). O reconhecimento dos tokens feito utilizando duas tcnicas principais:

Expresses Regulares e Autmatos Finitos. Trataremos das Expresses Regulares na


Seo 2.2.1. Para uma introduo Teoria dos Autmatos consulte Louden (2004)

e Aho, Sethi e Ullman (1988), para um estudo mais detalhado, consulte Hopcroft, Motwani e Ullman (2001).

2.2.1 Expresses Regulares


Segundo Jargas (2001):

Resumidamente, uma expresso regular um mtodo formal de se especicar um padro de texto. Mais detalhadamente, uma composio de smbolos, caracteres com funes especiais, que, agrupados entre si e com caracteres literais, formam uma sequncia, uma expresso. Essa expresso interpretada como uma regra, que indicar sucesso se uma entrada de dados qualquer casar com essa regra, ou seja, obedecer exatamente a todas as suas condies
As expresses regulares so uma importante notao para especicar os padres dos lexemas. Mesmo no podendo especicar todos os padres possveis elas so muito ecientes para o propsito de especicar os tokens que necessitamos.

Denies (segundo Aho et al. (2008)): Alfabeto Cadeia


qualquer conjunto nito de smbolos. Temos como exemplos de smbolos as letras, dgitos etc. O conjunto

{0, 1}

representa o alfabeto binrio.

em um alfabeto uma sequncia nita de smbolos retirados de alfabeto.

Normalmente, o tamanho da cadeia

dado por

|s|.

Por exemplo compilador , tem tamanho zero.

uma cadeia de tamanho 10. A cadeia vazia, indicada por

Linguagem

qualquer conjunto contvel de cadeias de algum alfabeto.

Expresso Regular Bsica so, simplesmente, os caracteres separados do


alfabeto que casam com eles mesmos. Por exemplo, dado que denimos o conjunto dos caracteres ASCII como nosso alfabeto, a expresso regular

/a/ casa com o caractere a.

H trs operaes bsicas utilizando Expresses Regulares conforme descritas abaixo (LOUDEN, 2004):

Escolha Entre Alternativas

Dado que

so expresses regulares, ento

r|s

r ou com a expresso s. Exemplo: dado que r seja a expresso regular /a/ e s a expresso regular /b/, r|s casa com
uma expresso regular que case com a expresso o caractere

a ou o caractere b.

Concatenao

r e s da pela expresso rs e casa com qualquer cadeia que case com a expresso regular r seguida pela expresso regular s. Exemplo: dado que r seja a expresso regular /ca/ e s a expresso regular /sa/, rs casa com a cadeia casa.
A concatenao de duas expresses regulares Tambm conhecida como fecho de Kleene, denotada por

Repetio

r,

em que

uma expresso regular. A expresso regular

representa o conjunto de cadeias

obtidas pela concatenao de zero ou mais expresses regulares a expresso regular ....

r.

Exemplo: dada

/a /, esta expresso casa com as cadeias

a, aa, aaa, aaaa,

Para simplicar a notao das expresses regulares comum associar nomes s expresses regulares longas. Uma Expresso Regular nomeada chamadas de

denio regular.
Alm das operaes descritas, a norma ISO/IEC 9945:2003 (2004) dene operaes adicionais chamadas Expresses Regulares Extendidas:

Uma ou Mais Repeties


r
dada por

A repetio de de uma ou mais vezes da expresso regular O mesmo

r+,

eliminando o casamento da expresso vazia ( ).

resultado poderia ser obtido com a expresso

rr,

mas esta uma situao to

frequente que foi simplicada e padronizada (LOUDEN, 2004).

Qualquer Caractere

Um . (ponto) utilizado para efetuar o casamento com qual-

quer caractere, exceto um caractere nulo.

H outras operaes que envolvem expresses regulares. Para mais informaes consulte Jargas (2001), ISO/IEC 9945:2003 (2004) e Louden (2004).

2.3

Anlise Sinttica

A Anlise Sinttica dene a forma com que um programa estruturado. Essa estrutura dada por um conjunto de regras gramaticais descritas em uma Gra-

mtica Livre de Contexto (vericar Seo 2.3.1).


Segundo Aho et al. (2008):

Existem trs estratgias gerais de anlise sinttica para o processamento de gramticas: universal, descendente e ascendente. Os mtodos de anlise baseados na estratgia universal (. . . ) podem analisar qualquer gramtica,

(. . . ) no entanto so muito inecientes para serem utilizados em compiladores de produo. Os mtodos geralmente usados em compiladores so baseados nas estratgias descendentes ou ascendentes. Conforme sugerido por seus nomes, os mtodos de anlise descendentes constroem as rvores de derivao de cima (raiz) para baixo (folhas), enquanto os mtodos ascendentes fazem a anlise no sentido inverso, comeam nas folhas e avanam at a raiz construindo a rvore. Em ambas as estratgias, a entrada do analisador sinttico consumida da esquerda para a direita, um smbolo de cada vez.
Para maiores referncias sobre analisadores descendentes (descendente recursivos e LL(k)), consulte Louden (2004), Parr (2007), Jacobs (1985).

2.3.1 Gramticas Livres de Contexto


Gramtica essencialmente um conjunto de Regras de Produo (ou Reescrita). Essas regras so, usualmente, descritas utilizando uma notao chamada

Forma de Backus-Naur, ou BNF (LOUDEN, 2004).


Um exemplo abstrato de regra de produo demonstrado abaixo:

A
Esta expresso indica que o no-terminal de terminais e/ou no-terminais representada por

ser substitudo pela sequncia

Um terminal, normalmente, um

token oriundo do analisador lxico.


Um exemplo mais concreto demonstrado abaixo:

expr expr + expr|numero


Esta regra indica que uma expresso composta de uma expresso seguida de um sinal de + seguida de outra expresso, ou de um nmero. O nome da regra dado pela parte que est a esquerda da seta, seu corpo dado pelo que est a direita. O sinal

| indica uma escolha de alternativas no corpo da produo.

Percebemos, tambm,

que uma regra gramatical pode ter uma denio recursiva. Segundo Louden (2004), podemos denir uma

texto mais formalmente conforme segue:


1. Um conjunto

gramtica livre de con-

de

terminais.

2. Um conjunto 3. Um conjunto e

N P

de de

no-terminais (disjunto de T ). produes na forma A em que A um elemento de N


(T N )
(uma sequncia de terminais e no-terminais que

um elemento de

pode ser vazia). 4. Um

smbolo inicial S

do conjunto

N.

Dessa forma, o processo de reconhecimento da linguagem inicia-se derivando o smbolo inicial da gramtica, substituindo repetidamente um no-terminal pelo corpo desse no terminal (AHO et al., 2008). Assim, uma

gramtica livre de contexto

uma gramtica conforme

denido a cima, e livre de contexto pois a parte a esquerda de uma regra de produo pode ser substituda pelo seu corpo em qualquer ponto, independentemente de onde ocorra a parte esquerda da regra (LOUDEN, 2004). Em contrapartida, uma produo em uma gramtica sensvel ao contexto demonstrada abaixo:

A
Nesta regra, terminais

pode ser substitudo por

somente se

estiver entre os

2.3.2 Ambiguidade
Uma gramtica dita ambgua quando ela pode gerar duas rvores de derivao distintas. Consideremos a gramtica da Listagem 1 e a expresso abaixo:

45 + 3 5

Listagem 1: Exemplo de Gramtica Ambgua


1 exp = exp op exp 2 | (exp) 3 | numero 4 ; 5 op = + | - | * 6 ;

Essa gramtica possibilita a gerao de duas rvores sintticas distintas conforme demonstrado na Figura 1. Gramticas que possuem essa caracterstica geram problemas para o analisador sinttico, pois no permitem representar com preciso a estrutura do programa.

45

45

Figura 1: rvores Sintticas Ambguas

A rvore a esquerda representa a expresso

(45 + 3) 5 enquanto a da direita representa

45 + (3 5).
Uma forma de resolver este problema utilizar uma regra de eliminao

de ambiguidade.

Regras de Precedncia e Associatividade so exemplos de regra de

eliminao de ambiguidade. Utilizando a precedncia usual da Matemtica, a forma preferida para interpretar a expresso direita na Figura 1(LOUDEN, 2004).

45 + 3 5

aquela demonstrada na rvore a

2.3.3 Anlise Sinttica Ascendente


O processo de anlise sinttica ascendente foi proposto por Donald E. Knuth em 1965. Em seu artigo, ele dene a anlise sinttica

LR(k).

O L indica que a

entrada processada da esquerda para a direita e o R indica que uma derivao a direita produzida, e a varivel

indica o nmero de smbolos de vericao a frente

utilizados pelo analisador. (LOUDEN, 2004). Ainda segundo Louden (2004), essa forma de anlise foi considerada impraticvel at que as tcnicas SLR e LALR foram desenvolvidas por DeRemer em 1969. Ainda assim, no prtica usual construir um analisador sinttico ascendente manualmente, mas utilizar um gerador que abstraia os detalhes de implementao (Vericar Seo 2.3.4). Mais detalhes sobre essas tcnicas podem ser obtidos em Knuth (1965), Deremer (1969), Aho et al. (2008). Os analisadores ascendentes utilizam uma pilha explcita (diferentemente dos analisadores recursivos, que utilizam a pilha implicitamente) durante o processo de anlise sinttica e, no geral, possuem duas aes possveis, alm da aceitao:

1. 2.

Carregar um terminal da entrada para o topo da pilha. Reduzir uma cadeia de terminais para um no-terminal A, dada a escolha da
regra

A .
Por causa dessa duas aes possveis, esse tipo de analisador tambm

conhecido como

carrega-reduz (ou shift-reduce ).

Listagem 2: Gramtica Exemplo


1 expressao = termo 2 ; 3 4 termo = termo + termo 5 | termo - termo 6 | fator 7 ; 8 9 fator = fator * fator 10 | (termo) 11 | numero 12 ;

Listagem 3: Programa Exemplo


1 10 * 20 + 30

Dados a gramtica exemplo da Listagem 2 e o programa da Listagem 3, e considerando os smbolos

T, F

como os no-terminais termo, fator e

como o

terminal numero, respectivamente, que numero representa um nmero inteiro, teremos os seguintes passos executados pelo analisador sinttico:

1. Carregar

na pilha.

2. Reduzir o topo da pilha para

e empilhar.

3. Carregar o token * na pilha. 4. Carregar o token

na pilha.

5. Reduzir o topo da pilha para 6. Reduzir 7. Reduzir

e empilhar.

F F F

para

F.

para

8. Carregar o token + na pilha. 9. Carregar o token 10. Reduzir 11. Reduzir 12. Reduzir 13. Reduzir

na pilha.

N F

para para

F T

e empilhar. e empilhar.

T +T T

para

T.

para expressao.

10

Nesse momento, o analisador sinttico retorna informando que o programa est correto, que a sequncia de tokens foi aceita como vlida para a gramtica denida. Mais detalhes de como so feitas as escolhas entre carregar um token e escolher a regra para efetuar uma reduo podem ser encontradas em Aho et al. (2008)

2.3.4 Geradores de Analisadores Sintticos Ascendentes


Conforme citado na Seo 2.3.3, no usual a implementao manual de um Analisador Sinttico Ascendente. Dessa forma, temos alguns geradores que simplicam esse processo para o implementador de um compilador. Um gerador amplamente utilizado o YACC (do ingls yet another compiler

compiler  mais um compilador de compiladores (LOUDEN, 2004).


Segundo Johnson (1975):

Yacc provides a general tool for imposing structure on the input to a computer program. The Yacc user prepares a specication of the input process; this includes rules describing the input structure, code to be invoked when these rules are recognized, and a low-level routine to do the basic input. Yacc then generates a function to control the input process. This function, called a parser, calls the user-supplied low-level input routine (the lexical analyzer) to pick up the basic items (called tokens) from the input stream. These tokens are organized according to the input structure rules, called grammar rules; when one of these rules has been recognized, then user code supplied for this rule, an action, is invoked; actions have the ability to return values and make use of the values of other actions.
Um arquivo de especicao YACC possui o formato bsico conforme o demonstrado na Listagem 4. Na seo de denies, primeira parte da especicao, includo entre os caracteres %{ e %} os trechos de cdigo que devero ser includos diretamente no analisador sinttico gerado. Normalmente, so includos nesse ponto da especicao os headers necessrios, bem como as declaraes de funes auxiliares necessrias. Ainda na seo de denies, so includas as denies da unio que armazenar os ns da rvore sinttica produzida (vericar Apndice A Listagem 22), dos tokens que esto presentes na gramtica a ser reconhecida, o tipo de retorno dos

no-terminais e a precedncia dos operadores binrios.

Listagem 4: Formato Especicao YACC


1 {definicoes} 2 %%

11

3 {regras} 4 %% 5 {rotinas auxiliares}

Na seo de regras, so denidas as regras sintticas da gramtica.

Para

isso utilizada uma notao BNF. Aps a denio de cada regra, includo entre os caracteres { e } um trecho de cdigo em linguagem C, que representa a ao semntica que dever ser executada quando aquela regra for encontrada. Por m, na seo de rotinas auxiliares so includas quaisquer funes que forem necessrias para a execuo do analisador sinttico. Comumente includa nessa seo a funo que informa os erros encontrados pelo analisador sinttico.

Listagem 5: Exemplo de especicao YACC


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
%{ #include <stdlib.h> #include <stdio.h> int yylex(void); int yyerror(const char *, ...); int resultado; %} %union{ int valor; } %token MAIS MENOS VEZES PARD PARE %token <valor> numero %% expressao = termo { resultado = $1; } ; termo = termo MAIS termo { $$ = $1 + $3 } | termo MENOS termo { $$ = $1 - $3 } | fator { $$ = $1 } ; fator = fator VEZES fator { $$ = $1 * $3 } | PARE termo PARD { $$ = $2 } | numero { $$ = yylval.valor } ; %% void yyerror(const char * s, ...) { printf("erro de sintaxe: %s\n", s); return; }

Na Listagem 5 temos um exemplo concreto de uma especicao para

YACC. Foi considerado, para essa especicao, que o analisador sinttico interagiria com o analisador lxico atravs da chamada de funo yylex(). Essa funo retorna um inteiro que representa o tipo de token reconhecido pelo analisador lxico. nosso exemplo temos as representaes Em

MAIS, MENOS, VEZES, PARE, PARD, que signicam, respectivamente os caracteres +, -, *, (, ).

12

O token numero tambm retornado pelo analisador lxico, mas alm do valor de retorno, disponibilizado na estrutura yylval o valor lxico correspondente ao token. possvel perceber na listagem 5 algumas pseudo-variveis (aquelas iniciadas pelo caractere $ nas aes semnticas). A varivel $$ indica o valor retornado pela ao semntica. As variveis representadas por $n em que

n {1, 2, 3, . . . }

representam os

valores retornados por cada no-terminal reconhecido. Por m, na seo de rotinas auxiliares, denida a funo yyerror() que ativada quando o analisador sinttico encontra algum erro.

2.4

Tabela de Smbolos
Segundo Aho et al. (2008),  Tabelas de Smbolos so estruturas de dados uti-

lizadas pelos compiladores para conter informaes sobre as construes do programafonte. Essas informaes so coletadas durante as fases de anlise (Lxica e Sinttica) e utilizadas durante a fase de gerao do programa-objeto (tambm conhecida como fase de sntese ). As entradas na tabela de smbolos contm informaes sobre identicadores; nome ou seu lexema, posio de memria, seu tipo, entre outras informaes que o implementador julgar necessrias. As principais operaes sobre Tabelas de Smbolos so inserir, consultar e

remover uma entrada. Dessa forma, precisamos de uma estrutura de dados que permita
executar essas operaes ecientemente. Foi escolhida a estrutura de Tabela de Hash

Encadeada para sua implementao (vericar Seo 2.4.1).


Tabelas de smbolos, tambm, so comumente utilizadas para manter informaes de escopo dos identicadores. Como neste projeto teremos apenas um escopo global, no discutiremos esse tema, entretanto, mais referncias podem ser encontradas em Aho et al. (2008) e Louden (2004).

2.4.1 Hashing (Transformao de Chave)


Hashing um mtodo de pesquisa que utiliza uma funo de transformao
da chave de pesquisa para calcular o endereo em que a entrada ser armazenada. Como podemos observar na Figura 2, temos as chaves inseridas na tabela. no endereo

6, 11, 16, 19, 22, 27 Tambm possvel perceber que a chave com valor 16 est inserida

da tabela. Para efetuar o mapeamento entre as chaves e os endereos

necessrio a utilizao de uma funo de hashing (ou funo de transformao ).

13

Figura 2: Tabela de Hashes

Uma

funo de hashing

deve mapear uma chave (e.g.

o nome de um o tamanho da

identicador) em um inteiro dentro do intervalo

[0..M 1]

em que

tabela. Considerando que as transformaes sobre as chaves so aritmticas, o primeiro passo transformar as chaves no-numricas em nmeros. Para isso, podemos utilizar, por exemplo, o valor inteiro conforme a Tabela ASCII (ZIVIANI, 2007).

h(K) = K mod M

(1)

A Equao 1 dene uma forma de transformar uma chave alfanumrica em um valor numrico tamanho

correspondente. Nela calculamos o resto da diviso de

pelo

do arranjo que armazenar a tabela.

n1

K=
i=0

chave[i]

i i

(2)

K
chave e

denido conforme a Equao 2. O produto por

o ndice do caractere na cadeia iguais

n o tamanho da cadeia.

i utilizado para evitar hashes

quando tratamos de anagramas. Nesse processo h grande possibilidade de duas chaves possurem hashes iguais, isto denominado coliso de hashes. Uma das formas possveis de resoluo de colises utilizar uma Lista Encadeada. Dessa forma, todas as chaves conitanUma demonstrao dessa

tes so encadeadas em uma lista linear (ZIVIANI, 2007). representao de dada na Figura 2.

Outras formas de resoluo de colises e outras implementaes de hashes (como Hashing Perfeito) podem ser encontrados em Knuth (1973) e Ziviani (2007).

14

2.5

Gerao de Cdigo
Gerao de Cdigo o processo de utilizar todas as informaes geradas

durante as fases de anlise (Lxica, Sinttica etc) para gerar o programa-objeto. Conforme a arquitetura do compilador, possvel incluir outras etapas intermedirias, conhecidas como Representaes Intermedirias (RI), que visam possibilitar otimizaes no programa-objeto gerado (LOUDEN, 2004). Uma forma possvel de RI conhecida com cdigo-de-trs-endereos. Este formato conhecido desta forma pois possui a seguinte forma de instrues

x = y opz ,

ou seja, do lado direito da atribuio possui apenas um operador binrio, seus operandos e do lado esquerdo a varivel que armazena o resultado da operao. Variaes so permitidas para representar, por exemplo, o sinal de menos unrio

x = y .

Listagem 6: Cdigo de 3 Endereos


1 2 3 4
t1 = c * d t2 = a + b t3 = t1 + t2 x = t3

A Listagem 6 demonstra um exemplo do cdigo-de-trs-endereos para a expresso

x = a + b + c d.

As variveis ti para

i {1, 2, 3}

representam variveis

temporrias criadas pelo prprio compilador. Para este projeto, no so geradas RIs, apenas os programas-objeto em

Linguagem C, que posteriormente podem ser compiladas por um compilador C, como


o gcc, gerando um programa executvel, e em Linguagem DOT possibilitando a gerao de uma representao grca do programa. Mais referncias sobre RIs e cdigos-detrs-endereos so encontradas em Aho et al. (2008) e Louden (2004). Conforme exposto, este projeto de compilador atua como um tradutor entre linguagens. Uma abordagem semelhante foi utilizada na implementao inicial da

Linguagem C++. Este compilador traduzia programas C++ para programas C para
que pudessem, posteriormente, ser compilados por um compilador C disponvel. Assim, podemos considerar o processo de compilao como um processo de traduo de uma linguagem de nvel mais alto para uma outra linguagem de nvel mais baixo, repetindo o processo at que seja produzido um programa executvel na mquina-alvo (AHO et al., 2008). A Gerao de Cdigo tambm consiste num processo de linearizao das estruturas de rvores disponibilizadas pelas fases anteriores, transformando, por exemplo, uma rvore sinttica em um programa C, em que as instrues so escritas linearmente em um arquivo.

15

A Listagem 7 demonstra uma possvel implementao, em pseudocdigo, de funo geradora de cdigo, tendo como base uma rvore sinttica em que cada n possui at dois lhos. Notamos que a funo pode vistar a rvore em pr-ordem, em ordem e ps-ordem.

Listagem 7: Exemplo Gerador de Cdigo


1 funcao geraCodigo (no_arvore T) 2 inicio 3 gerar_codigo_preparatorio(T) 4 gerar_codigo(T) 5 gerar_codigo_preparatorio_filho_esquerda(T->filho_esquerda) 6 gerar_codigo_filho_esquerda(T->filho_esquerda) 7 gerar_codigo_preparatorio_filho_direita(T->filho_direita) 8 gerar_codigo_filho_direita(T->filho_direita) 9 gerar_codigo_final(T) 10 fim

Com pequenas alteraes no cdigo da Listagem 7, podemos incluir mais lhos aos ns lhos rvore

T,

bem como, representar a construo de quase todas as

construes necessrias para produzir o programa-objeto.

2.5.1 Linguagem DOT


Segundo Ellson et al. (2003):

Graphviz is a collection of software for viewing and manipulating abstract graphs. It provides graph visualization for tools and web sites in domains such as software engineering, networking, databases, knowledge representation, and bio-informatics
Um dos softwares dessa coleo o compilador dot. Koutsoos e North (2009): Segundo Gansner,

dot draws directed graphs. It reads attributed graph text les and writes drawings, either as graph les or in a graphics format such as GIF, PNG, SVG, PDF, or PostScript.

dot aceita como entrada um arquivo de texto expresso na Linguagem DOT


(vericar

http://graphviz.org/content/dot-language).

Essa linguagem

dene trs tipos principais de objetos: grafos, ns e arestas. O grafo principal (mais externo) pode ser direcionado (digraph  directed graph, ou seja, grafo direcionado), ou no-direcionado. Em um grafo principal, possvel termos um subgrafo (subgraph ) que permite a denies de ns e arestas (GANSNER; KOUTSOFIOS; NORTH, 2009). Um n criado quando o seu nome aparece pela primeira vez no arquivo. As arestas so criadas quando dois ns so ligados pelo operador de aresta ->.

16

Listagem 8: Exemplo de Grafo Expresso em DOT


1 digraph G { 2 principal 3 principal 4 principal 5 execucao 6 execucao 7 inicializacao 8 principal 9 execucao 10 }
-> -> -> -> -> -> -> -> analise -> execucao; inicializacao; limpeza; gera_cadeia_caracteres; imprime_formatado; gera_cadeia_caracteres; imprime_formatado; compare;

Na Listagem 8 temos o exemplo de um grafo escrito em DOT que aps sua compilao com o comando

dot

-Tpng

exemplo_dot.gv

>

exemplo_dot.png

gerar a representao grca demonstrada na Figura 3.

Figura 3: Exemplo Grafo Gerado pelo dot

17

3 Implementao
Para a implementao desse trabalho, foi denida a gramtica simples que opera apenas sobre inteiros. No ha ativao de funes e apenas um escopo (global). Na Listagem 9, temos um programa-exemplo para o clculo da sequncia de Fibonacci.

Listagem 9: Exemplo de Clculo da Sequncia de Fibonacci


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
n0 = 0; n1 = 1; naux = 0; i = 0; leia n; n = n - 1; se (n == 0) escreva n; senao enquanto( i < n ) naux = n1; n1 = n0 + n1; n0 = naux; i = i + 1; fim; escreva n1; fim;

Vericamos no exemplo da Listagem 9, que a linguagem suporta construes comumente vistas numa linguagem de programao mais sosticada. Temos uma srie de atribuies, leitura da entrada-padro, estrutura condicional (se-seno ), estrutura de lao (enquanto ) e escrita para a sada-padro. Entretanto no h suporte para

vetores (arrays ), nmeros de ponto utuante e cadeias de caracteres (strings ), recursos presentes nas linguagens de programao reais. Mesmo na ausncia destes recursos, nossa linguagem possibilita o estudo da implementao e funcionamento de um compilador. A denio formal da linguagem encontra-se na Listagem 10.

Listagem 10: Gramtica reconhecida


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
program : stmts ; stmts : stmts stmt | stmt ; stmt : | | | | ; if_decl SEMI while_decl SEMI attrib_decl SEMI read_decl SEMI write_decl SEMI

if_decl

: IF LPAREN bool RPAREN stmts END | IF LPAREN bool RPAREN stmts ELSE stmts END ;

while_decl : WHILE LPAREN bool RPAREN stmts END ; attrib_decl : ID ATTR expr

18

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

; read_decl : READ ID ; write_decl : WRITE ID ; expr : | | | | | ; bool : | | | | | | | | ; expr PLUS expr expr MINUS expr expr TIMES expr expr OVER expr factor bool expr expr expr expr expr expr expr expr expr OR expr AND expr EQ expr NEQ expr GT expr LT expr GE expr LE expr

factor : LPAREN expr RPAREN | ID | NUM ;

Como podemos perceber um programa um conjunto de instrues delimitados por ponto-e-vrgula. A linguagem disponibiliza uma instruo para execuo condicional e uma outra para laos de repetio. Ambas tomam como parmetro uma expresso que possa ser avalidada como um inteiro. Como ocorre na Linguagem C uma expresso cujo valor seja avaliado como 0 (zero) considerada falsa e qualquer outro valor avaliado como verdadeiro. Adicionalmente as instrues de lao e condicionais, a linguagem propicia uma instruo para o armazenamento da avaliao de uma expresso (atribuio de varivel) e duas instrues de Entrada e Sada. A instruo de entrada-padro (leia ) l um inteiro do teclado e a instruo de sada (escreva ) escreve o valor atribudo a uma varivel na sada-padro (possivelmente um terminal). As operaes relacionais (booleanas) possuem prioridades conforme a Tabela 1, sendo que as operaes que aparecem primeiro, na tabela, possuem maior prioridade. As operaes matemticas possuem a precedncia usual. < <= > >= == != Menor, Menor ou igual, Maior, Maior ou igual Igualdade, Desigualdade

Tabela 1: Prioridades dos Operadores Relacionais

A gramtica da Listagem 10 ambgua (vericar Seo 2.3.2), todavia as ambiguidades so resolvida pelas prioridades j discutidas, durante o processo de anlise sinttica.

19

Para a implementao do compilador foi escolhida a linguagem C. A escolha foi baseada na familiaridade do autor com a linguagem, alm da farta disponibilidade de ferramentas e literatura sobre a referida linguagem. Mais informaes podem ser obtidas em Banahan, Brady e Doran (1991), Kernighan e Ritchie (1999) e Schildt (1995)

Figura 4: Mdulos do Compilador

A Figura 4 demonstra o esquema de interao entre os mdulos do compilador. O programa-fonte lido pelo Analisador Lxico que produz o uxo de tokens que alimenta o Analisador Sinttico. Este verica se os tokens recebidos so corretos para a gramtica determinada, produz a rvore Sinttica e insere e consulta entradas na Tabela de Smbolos. O Gerador C navega na rvore Sinttica para gerar as construes correspondentes em C, consultando a Tabela de Smbolos quando necessrio. O Gerador DOT monta a representao grca apenas com as informaes disponibilizadas pela rvore Sinttica. Por m, compilamos o programa C gerado, com um compilador C padro, para obtermos o programa executvel e compilamos o arquivo DOT (Grafo DOT ) com o aplicativo grca da lgica do programa-fonte.

dot (Seo 2.5.1) para obtermos a representao

3.1

Estruturas de Dados
As principais Estruturas de Dados esto denidas na Listagem 20 (global.h).

So denidos trs enumeradores que sero utilizados para diferenciar os ns da rvore Sinttica: node_kind, stmt_kind e expr_kind. se determinado n uma O enumerador node_kind especica Caso seja uma

instruo ou expresso.

instruo, veri-

camos seu tipo no enumerador stmt_kind que possui como valores possveis: if_k,

while_k, attrib_k, write_k, read_k, para as instrues de escolha condicional, lao de


repetio, atribuio de varivel, escrita e leitura, respectivamente. Caso seja o n seja uma

expresso,

vericamos o enumerador expr_kind, que nos possibilita os valores

seguintes: op_k, id_k e const_k, para indicar expresses aritmticas e booleanas, a presena de um identicador ou uma constante numrica, respectivamente. Os tokens para constantes numricas e identicadores so armazenados

20

numa estrutura chamada token_t.

A estrutura mantm a linha em que o token foi

encontrado e uma unio que armazena um inteiro (constante numrica), ou um ponteiro para uma cadeia de caracteres (identicadores). Para os demais casos, apenas o tipo do token retornado pelo analisador lxico. A rvore Sinttica produzida por ns da estrutura do tipo node_t. A

sequncia de instrues representada por uma lista ligada utilizando o apontador

next. As instrues aninhadas (repeties e condicionais) so representadas como ns


lhos. Para os ns que representam expresses o atributo da expresso armazenado numa unio conforme o tipo da expresso (operao aritmtica, um identicador, ou uma constante numrica).

3.2

O Programa Principal
O programa principal, compiler.c, bastante simples e est disponvel no

Apndice A na Listagem 19. Primeiramente so conguradas as variveis de chaveamento, com base nas opes passadas na linha de comando. Para isso so feitas sucessivas chamadas a funo

getopt(), presente na biblioteca-padro da linguagem C.


So conguradas as variveis que armazenam as referncias para o arquivo de entrada (que ser utilizada pelo Analisador Lxico) e para a Tabela de Smbolos (utilizada no Analisador Sinttico). Analisador Sinttico (yyparse() ). Ativaes adicionais como imprimir rvore sinttica e tabela de smbolos, gerar cdigo C (Seo 3.6.1) e DOT (3.6.2), so feitas conforme as opes passadas na linha de comando. Em seguida, chamada a funo que ativa o

3.3

Anlise Sinttica

O Analisador Sinttico foi produzido utilizado o cdigo gerado pela ferramenta GNU/Bison (CORBETT; STALLMAN, 2008). Bison uma implementao do

YACC (Yet Another Compiler Compiler  Um Outro Compilador de Compiladores),


um gerador de analisadores sintticos ascendentes que recebe um arquivo de especicaes da gramtica a ser reconhecida (descrito numa gramtica livre de contexto) e produz cdigo C para cada regra sinttica reconhecida. O arquivo da especicao est listado no Apndice A na Listagem 22,

21

arquivo parser.y.

A especicao dividida em 3 partes, separadas pelos caracteres

%%.

Na primeira, so includas o prembulo e as declaraes de congurao do

Bison. Na segunda, esto as regras gramaticais e suas respectivas regras semnticas.


Na terceira, inclui-se quaisquer funes auxiliares que forem necessrias. Na seo inicial da especicao, o cdigo listado entre %{ e %} ser includo diretamente no arquivo gerado, em seguida denido, como uma unio, o tipo de dados retornado pelo analisador gerado, bem como a declarao dos tokens (que nesse contexto tambm so os smbolos terminais da gramtica) e as precedncias das operaes ambguas. As regras gramaticais, na segunda seo do arquivo de especicao, so idnticas quelas listadas no incio deste captulo (Seo 3), acrescidas das aes semnticas equivalentes. O exemplo da gramtica para a instruo de escrita de um

inteiro encontra-se na Listagem 11. Este trecho de cdigo indica que o Analisador Sinttico deve receber um token

WRITE seguido de um token ID do Analisador Lxico.


Tambm

Quando isso ocorrer criado um novo n de instruo do tipo escrita, que ser armazenado na pseudo-varivel

$$ para ser retornado e includo na rvore Sinttica.

so armazenados no n o nome do Identicador (varivel) que dever ser escrita e a sua localizao no arquivo-fonte.

Listagem 11: Instruo de Escrita


1 write_decl : WRITE ID 2 { 3 $$ = new_stmt_node(write_k); 4 $$->child[0] = new_expr_node(id_k); 5 $$->child[0]->attr.name = copy_str ((yylval.token)->value.name); 6 $$->lineno = yylval.token->lineno; 7 } 8 ;

As demais regras so bastantes parecidas com aquela demonstrada na Listagem 11, exceto pela regra que dene a atribuio de uma varivel. Para as atribuies (vericar Listagem 12) necessrio declarar uma regra implcita para armazenarmos os valores do token

ID antes de construirmos o n correspondente.

Isso necessrio,

pois o Analisador Sinttico s sabe que se trata de uma instruo de atribuio depois de reconhecer o no-terminal expr. Quando expr reconhecido as referncias para o

ID j foram perdidas. Outro detalhe que chama a ateno na Listagem 12 a presena da pseudo-varivel $4. O Bison nomeia cada terminal e no-terminal de uma
token produo com o caractere $ seguido com um nmero

n em que n o ndice do terminal

ou no-terminal da produo, iniciado em 1. No caso da Listagem 12 temos 3 terminais e no-terminais, sendo expr o terceiro, dessa forma o ndice de valor 4 aparece devido a instruo implcita para armazenar, temporariamente, as referncias para o token

ID.

22

Listagem 12: Instruo de Atribuio


1 attrib_decl : ID 2 { 3 saved_name = copy_str ((yylval.token)->value.name); 4 lineno = yylval.token->lineno; 5 } 6 ATTR expr 7 { 8 $$ = new_stmt_node(attrib_k); 9 $$->child[0] = $4; 10 $$->attr.name = saved_name; 11 $$->lineno = lineno; 12 symtab_insert(stab, saved_name); 13 } 14 ;

Mais referncias sobre o YACC e Bison podem ser encontradas em Johnson (1975), Levine, Mason e Brown (1992), Levine (2009)

3.4

Anlise Lxica

O Analisador Lxico foi implementado com o auxlio da ferramenta Flex. Segundo o site do projeto (FLEX. . . , ):

Flex is a tool for generating scanners. A scanner, sometimes called a tokenizer, is a program which recognizes lexical patterns in text. The ex program reads user-specied input les, or its standard input if no le names are given, for a description of a scanner to generate. The description is in the form of pairs of regular expressions and C code, called rules. Flex generates a C source le named, "lex.yy.c", which denes the function yylex(). The le "lex.yy.c"can be compiled and linked to produce an executable. When the executable is run, it analyzes its input for occurrences of text matching the regular expressions for each rule. Whenever it nds a match, it executes the corresponding C code.
A especicao Flex para o Analisador Lxico encontra-se no Apndice A Listagem 21. O arquivo, assim como a especicao do Analisador Sinttico, di-

vidido em trs sees separadas por um par de caracteres %% : denies, regras de reconhecimento e funes auxiliares. Na primeira seo do arquivo, o segmento de cdigo C delimitado por %{ e %} copiado diretamente para o arquivo gerado. Ainda nesta seo, so denidas as opes para a execuo do Flex, bem como as denies regulares que sero utilizadas nas regras de reconhecimento dos tokens. As regras de reconhecimento, na segunda seo do arquivo, so pares Expresses Regulares-Blocos de Cdigo. As Expresses Regulares so instrues de casamento para os tokens, enquanto os Blocos de Cdigo so os trechos que devero ser

23

executados quanto uma determinada cadeia de caracteres do programa-fonte casar com uma das Expresses Regulares. As possveis ambiguidades nas regras de casamento so resolvidas com o casamento da cadeira mais longa. Na persistncia da ambiguidade, a regra que foi denida primeiro ganha precedncia. Caso seja encontrado algum caractere no permitido no programa-fonte uma mensagem de erro emitida. A seo de funes auxiliares opcional e no foi necessria nesta implementao.

3.5

Tabela de Smbolos

Dada a simplicidade da gramtica proposta (h apenas um escopo global, . . . ), a tabela de smbolos necessria apenas para manter os nomes e localizao, no programa-fonte, das variveis declaradas. No houve a necessidade de manter os tipos das variveis, pois por denio, h apenas operaes sobre nmeros inteiros. Sua implementao foi feita baseada numa Tabela de Hashs de enderea-

mento aberto e a Listagem est disponvel no Apndice A Listagens 25 e 26. Para mais
informaes, consulte a Seo 2.4.1. As variveis so includas na tabela, pelo analisador sinttico, durante o reconhecimento de uma instruo de leitura ou atribuio, na primeira vez em que ela reconhecida. A incluso efetuada calculando-se hash do nome da varivel e mapeando o valor do hash para um endereo na tabela de smbolos (que, tambm, uma tabela de hashes). Caso o endereo esteja disponvel, uma entrada criada neste endereo. Caso o endereo j esteja ocupado, o conito resolvido com a criao de uma lista ligada, incluindo a nova entrada no nal da lista.

3.6

Gerao Cdigo

A fase nal no processo de compilao para nossa implementao a Gerao de Cdigo. Para este projeto, foram escolhidas duas representaes como produto do processo de compilao, uma representao em Linguagem C e outra em Linguagem DOT. Os detalhes de implementao so discutidos nas Sees 3.6.1 e 3.6.2, respectivamente. Os cdigos-fonte referentes a gerao de cdigo C esto listados nas Listagens 28 e 28, os referentes a gerao de cdigo DOT, nas Listagens 29 e 30, ambas no

24

Apndice A.

3.6.1 Gerao Cdigo C


O programa-objeto em Linguagem C gerado pela funo generate_c() ativada pelo programa principal (Apndice A Listagem 19). So requeridos pela funo trs parmetros: o arquivo em que o programa-objeto ser escrito, o ponteiro para a rvore Sinttica e o ponteiro para a Tabela de Smbolos. A funo, primeiramente, inclui no arquivo de sada os headers necessrios e o incio da declarao do corpo da funo main(). Neste momento, faz-se uso da Tabela de Smbolos. Em C, as variveis precisam ser declaradas antes de serem utilizadas,

ento a funo declare_variables() ativada. Esta funo varre a Tabela de Smbolos e inclui a declarao de todas as variveis necessrias no arquivo de sada. Em seguida, a funo gen_c() ativada. Esta a funo mais importante desta fase da compilao. Esta funo, com base no tipo de n recebido como pa-

rmetro, faz chamadas para as funes correspondentes que emitem as instrues necessrias para o arquivo de sada. As demais funes, emit_while() por exemplo (vericar Listagem 13), geram os trechos de cdigo C correspondentes e fazem novas chamadas a gen_c() para cada um dos ns lhos.

Listagem 13: Funo geradora do comando while em C


1 void emit_while (FILE * cfile, struct node_t * node) 2 { 3 fprintf (cfile, "while ("); 4 gen_c (cfile, node->child[0]); 5 fprintf (cfile, ")\n{\n"); 6 gen_c (cfile, node->child[1]); 7 fprintf (cfile, "}\n"); 8 return; 9 }

As funes que requerem Entrada e Sada (I/O) so implementadas com chamadas as chamadas scanf() para Entrada e printf() para a sada, ambas funes contidas na biblioteca-padro da linguagem C. Como exemplo do resultado produzido, tomemos com exemplo o programa que calcula a Sequncia de Fibonacci apresentado na Seo 3 Listagem 9. Aps compilado, este programa resultar no programa C apresentado na Listagem 14.

Listagem 14: Programa Fibonacci Compilado em C


1 #include <unistd.h> 2 #include <stdlib.h> 3 #include <stdio.h>

25

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

int main (int argc, char **argv) { int i, naux, n, n0, n1; n0 = 0; n1 = 1; naux = 0; i = 0; scanf("%d", &n); n = n - 1; if (n == 0) { printf("%d", n); } else { while (i < n) { naux = n1; n1 = n0 + n1; n0 = naux; i = i + 1; } printf("%d", n1); } exit(EXIT_SUCCESS) }

Um outro exemplo (programa que calcula um nmero fatorial) apresentado nas Listagens 15 e 16.

Listagem 15: Programa Fatorial


1 2 3 4 5 6 7
leia n; produto = 1; enquanto(n>1) produto = produto * n; n = n -1; fim; escreva produto;

Listagem 16: Programa Fatorial Compilado em C


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main (int argc, char **argv) { int produto, n; scanf("%d", &n); produto = 1; while (n > 1) { produto = produto * n; n = n - 1; } printf("%d", produto); exit(EXIT_SUCCESS) }

3.6.2 Gerao Cdigo DOT


A implementao para a linguagem DOT bastante parecida com aquela para a Linguagem C, embora seja um pouco mais complexa. necessrio manter

uma varivel de contexto entre as chamadas de funo que geram o cdigo DOT. Essa

26

varivel auxilia na gerao apropriada dos ns de retorno de uma instruo de repetio e o n da prxima instruo de uma condicional. Para isso, as chamadas de funo que geram cdigo DOT possuem um parmetro adicional que armazena esse n de contexto. A funo generate_dot() ativada pelo programa principal (Apndice A Listagem 19) tendo como parmetros apenas o ponteiro para o arquivo que armazenar o programa-objeto e o ponteiro para a rvore Sinttica. A Tabela de Smbolos no necessria, pois no h a necessidade de declarao prvia das variveis utilizadas. As duas funes principais para a gerao do arquivo DOT so dot_gen_graph() e dot_gen_shapes. A funo dot_gen_graph() funciona de forma semelhante a funo

gen_c() discutida na Seo 3.6.1.


A funo dot_gen_shapes() verica cada n da rvore Sinttica e escreve no programa-objeto qual a forma que um n deve assumir. Para esta implementao, apenas os ns de instruo condicional e lao de repetio possuem forma de losangolo, as demais instrues possuem o formato padro de retngulo. Para mais informaes sobre a linguagem DOT, consulte a pgina, na internet, do projeto Graphviz (vericar na Bibliograa).

27

4 Concluso
Acreditamos que objetivo principal deste trabalho, implementar um compilador que pudesse gerar uma representao grca da lgica do programa-fonte, foi cumprido. Temos uma implementao funcional de um compilador que gera como objetos um programa C e uma representao de grafo em DOT que pode ser convertido para uma imagem. Exemplos dos resultados obtidos com o gerador de cdigo C esto demonstrados na Seo 3.6.1 nas Listagens 14 e 16. Utilizamos o termo exemplos de resultados pois no possvel determinar todos os programas que sero escritos e compilados com nosso utilitrio. Os resultados obtidos com o gerador DOT no foram, exatamente, aqueles esperados (vericar discusso na Seo 4.1). A Listagem 17 demonstra o resultado atual da compilao do programa listado na Listagem 9 (Fibonacci). A Figura 5 demonstra a imagem gerada pela compilao do arquivo DOT.

Listagem 17: Grafo DOT Fibonacci


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
digraph program { node [shape=box]; attrib139797576 -> attrib139797736; attrib139797736 -> attrib139797896; attrib139797896 -> attrib139798056; attrib139798056 -> read139798144; read139798144 -> attrib139798488; attrib139798488 -> if139800024; if139800024 -> write139798744 [label ="true"]; write139798744 -> write139800096; if139800024 -> while139799984 [label ="false"]; while139799984 -> attrib139799192 [label = "true"]; attrib139799192 -> attrib139799496; attrib139799496 -> attrib139799672; attrib139799672 -> attrib139799944; attrib139799944 -> while139799984; while139799984 -> write139800096 [label = "false"]; write139800096; if139800024 [shape=diamond];while139799984 [shape=diamond];}

Ainda que no tenhamos obtido, exatamente, o resultado esperado com a representao grca, acreditamos que a ferramenta concebida neste projeto ainda pode auxiliar programadores novatos em processo de aprendizagem, pois ainda que de forma limitada, possvel observar o uxo lgico do programa na representao grca obtida. O projeto possui outras limitaes e possveis melhorias para suprir essas decincias so discutidas na Seo 4.1. Sugestes para um prximo projeto de pesquisa so expostos na Seo 4.2.

28

Figura 5: Representao Grca Atual do Programa Fibonacci

29

4.1

Limitaes e Melhorias

A limitao principal da gramtica, percebida por programadores mais experientes, a caracterstica da linguagem operar apenas sobre o conjunto dos nmeros inteiros. No h operaes de ponto utuante nem vetores. Caracteres e cadeias de caracteres (strings ) tambm no so suportados. Acreditamos que programadores novatos, exatamente por serem novatos, no sejam afetados por essa percepo. Esta

limitao de tipos de dados afetam apenas a gerao dos programas-objetos C, pois no criam novas instrues, apenas novas expresses. Alm disso, possvel operar

sobre os novos tipos apenas com as instrues de atribuio, leitura, escrita, repetio e condicional j implementadas. A inexistncia de denies de procedimentos e ativao de funes uma limitao mais severa, pois para que sejam implementadas necessria uma alterao profunda da gramtica e, por consequncia, em todos os mdulos do compilador. Conforme exibido na Figura 5, percebemos que apenas as instrues so apresentadas na imagem que representa a lgica do programa-fonte. Os rtulos dos

ns apresentam apenas o nome do n, que consiste na concatenao nome do seu tipo com o endereo de memria em que ele estava alocado no momento da compilao. A melhoria consiste em executar uma passada adicional na rvore sinttica para gerar os rtulos corretamente. Um exemplo do resultado esperado apresentado na Listagem 18. A respectiva imagem apresentada na Figura 6

Listagem 18: Melhoria Grafo DOT do Programa Fibonacci


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
digraph program { node [shape=box]; attrib139797576 [label = "n0 = 0"]; attrib139797736 [label = "n1 = 1"]; attrib139797896 [label = "naux = 0"]; attrib139798056 [label = "i = 0"]; read139798144 [label = "leia n"]; attrib139798488 [label = "n = n - 1"]; if139800024 [label = "n == 0"]; write139798744 [label = "escreva n"]; while139799984 [label = "i < n"]; attrib139799192 [label = "naux = n1"]; attrib139799496 [label = "n1 = n0 + n1"]; attrib139799672 [label = "n0 = naux"]; attrib139799944 [label = "i = i + 1"]; write139800096 [label = "escreva n1"]; attrib139797576 -> attrib139797736; attrib139797736 -> attrib139797896; attrib139797896 -> attrib139798056; attrib139798056 -> read139798144; read139798144 -> attrib139798488; attrib139798488 -> if139800024; if139800024 -> write139798744 [label = "true"]; write139798744 -> write139800096; if139800024 -> while139799984 [label = "false"]; while139799984 -> attrib139799192 [label = "true"]; attrib139799192 -> attrib139799496; attrib139799496 -> attrib139799672;

30

29 30 31 32 33

attrib139799672 -> attrib139799944; attrib139799944 -> while139799984; while139799984 -> write139800096 [label = "false"]; write139800096; if139800024 [shape=diamond];while139799984 [shape=diamond];}

4.2

Sugestes para Projetos Futuros

Com o propsito de nortear a continuidade deste projeto segue uma lista de sugestes para possveis melhorias:

Implementar operaes para nmeros de ponto utuante e caracteres; Implementar estruturas de vetores e matrizes; Implementar ativao de funes; Corrigir os rtulos dos ns; Melhorar a representao grca, tornando-a mais parecida com um uxograma, inclusive incluindo outros formatos para os ns.

31

Figura 6: Melhoria da Representao Grca do Programa Fibonacci

32

Bibliograa
AHO, A. V. et al. Compiladores: Principios, Tcnicas e Ferramentas. 2. ed. So Paulo: Pearson Addison-Wesley, 2008. AHO, A. V.; SETHI, R.; ULLMAN, J. D. Compilers: Principles, Techniques and

Tools. [S.l.]: Addison-Wesley, 1988.


BANAHAN, M.; BRADY, D.; DORAN, M. The C Book. 1991. Disponvel em

http://publications.gbdirect.co.uk/c_book/.

Acessado em Maio/2011.

CORBETT, R.; STALLMAN, R. Bison Manual. [S.l.]: Free Software Fundation, 2008. Disponvel em

http://www.gnu.org/software/bison/manual/.

Acessado

em Maro/2011. DEREMER, F. L. Practical Translators for LR(K) Languages. Cambridge, MA, USA, 1969. ELLSON, J. et al. Graphviz and Dynagraph  Static and Dynamic Graph Drawing

Tools. 2003. Disponvel em "http://graphviz.org/Documentation/

EGKNW03.pdf,

Acessado em Jun/2011".

FERREIRA, A. B. de H. Novo Dicionrio da Lngua Portuguesa. 2. ed. Rio de Janeiro, RJ, Brasil: Editora Nova Fronteira S.A., 1986. FLEX Project. Disponvel em Maro/2011. GANSNER, E. R.; KOUTSOFIOS, E.; NORTH, S. Drawing graphs with dot. 2009. Disponvel em "http://graphviz.org/pdf/dotguide.pdf". Acessado em Jun/2011. HOPCROFT, J. E.; MOTWANI, R.; ULLMAN, J. D. Introduction to automata

http://flex.sourceforge.net/.

Acessado em

theory, languages and computation. Boston, Massachusetts, USA: Addison-Wesley,


2001. ISO/IEC 9945:2003. ISO/IEC 9945:2003: Single UNIX Specication, Version 3. [S.l.], 2004.

33

JACOBS, C. Some topics in parser genaration. Vrije Universiteit, Amsterdam, 1985. JARGAS, A. M. Expresses Regulares - Guia de Consulta Rpida. So Paulo, SP, Brasil: Editora Novatec, 2001. JOHNSON, S. C. Yacc:

Yet Another Compiler-Compiler. Murray Hill, New

Jersey, USA: AT&T Bell Laboratories, 1975. Disponvel em

http://dinosaur.

compilertools.net/yacc/index.html.

Acessado em Abril/2011.

KERNIGHAN, B. W.; RITCHIE, D. M. C - A Linguagem de Programacao Padrao

ANSI. Rio de Janeiro, RJ, Brasil: Elsevier Editora LTDA, 1999.


KNUTH, D. E. On the translation of languages from left to right. Information and

control, n. 8, p. 607639, 1965.


KNUTH, D. E. Sorting and searching. The Art of Computer Programming, Addison-Wesley, n. 3, 1973. LEVINE, J. Flex & Bison. Sebastopol, CA, USA: O'Reilly Media Inc., 2009. LEVINE, J. R.; MASON, T.; BROWN, D. Lex & Yacc. Sebastopol, CA, USA: O'Reilly Media Inc., 1992. LOUDEN, K. C. Compiladores: Princpios and Prtica. So Paulo: Thomson Pioneira, 2004. ISBN 8522104220. PARR, T. Language implementation patterns. [S.l.]: Pragmatic Programmers, 2007. SCHILDT, H. C - Completo e Total. So Paulo, SP, Brasil: Makron Books, 1995. ZIVIANI, N. Projeto de Algoritmos com implementao em Java e C++. So Paulo, SP, Brasil: Thomson Learning, 2007.

34

A Listagem dos cdigos-fonte


Listagem 19: compiler.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
#include #include #include #include #include #include #include #include #include <stdlib.h> <stdio.h> <unistd.h> "global.h" "util.h" "compiler-parser.h" "symtab.h" "cgen.h" "dotgen.h"

FILE *yyin; extern struct symtab_t ** stab; void usage (char * arg); int main(int argc, char **argv, char **env) { FILE *cfile = NULL, *dotfile = NULL; int print_st = 0, print_ast = 0; int opt; while ((opt = getopt(argc, argv, "std:c:")) != -1) { switch(opt) { case s: print_st = 1; break; case t: print_ast = 1; break; case d: dotfile = fopen(optarg, "w"); break; case c: cfile = fopen(optarg, "w"); break; default: usage(argv[0]); exit(EXIT_FAILURE); } } if (optind >= argc) { fprintf (stderr, "Error: missed usage(argv[0]); exit(EXIT_FAILURE); }

input file.\n");

yyin = fopen(argv[optind], "r"); stab = symtab_new(); if (yyparse() != 0) { fprintf(stderr, "Compilation failure!\n"); exit(EXIT_FAILURE); } if (print_ast) print_tree(ast); if (print_st) symtab_print(stab); if(cfile) generate_c (cfile, ast, stab); if (dotfile) generate_dot (dotfile, ast); return EXIT_SUCCESS; } void usage (char * arg)

35

73 { 74 fprintf(stderr, "Usage: %s [-s] [-t] [-d <DOT_file_name>] [-c <C_file_name>] <input_file>\n", arg); 75 return; 76 }

Listagem 20: global.h


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#ifndef _GLOBAL_H_ #define _GLOBAL_H_ #include "symtab.h" #define MAX_CHILDREN 3 enum node_kind {stmt_k, expr_k}; enum stmt_kind {if_k, while_k, attrib_k, write_k, read_k}; enum expr_kind {op_k, id_k, const_k}; struct token_t { int lineno; union { int val; char *name; } value; }; struct node_t { struct node_t *child[MAX_CHILDREN]; struct node_t *next; int lineno; enum node_kind kind; union { enum stmt_kind stmt; enum expr_kind expr; } type; union { int op; int val; char *name; } attr; }; struct node_t * ast; struct symtab_t ** stab; #endif /* _GLOBAL_H_ */

Listagem 21: scanner.l


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
%{ #include "compiler-parser.h" #include <string.h> #include "global.h" #include "util.h" extern YYSTYPE yylval; %} %option 8bit %option warn nodefault %option yylineno noyywrap ws [[:blank:]\n]* identifier [[:alpha:]][[:alnum:]]* number 0|[1-9][0-9]* %% "e" "ou" "se" "senao" "enquanto" "leia" "escreva" return return return return return return return AND; OR; IF; ELSE; WHILE; READ; WRITE;

36

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

"fim" "==" "!=" "=" ">=" ">" "<=" "<" "+" "-" "*" "/" ";" "(" ")" {number}

{identifier}

{ws} .

return END; return EQ; return NEQ; return ATTR; return GE; return GT; return LE; return LT; return PLUS; return MINUS; return TIMES; return OVER; return SEMI; return LPAREN; return RPAREN; { yylval.token = new_token(); (yylval.token)->value.val = atoi(yytext); return NUM; } { yylval.token = new_token(); (yylval.token)->value.name = copy_str (yytext); (yylval.token)->lineno = yylineno; return ID; } /* ignore */; printf("bad input character %s at line %d\n", yytext, yylineno);

Listagem 22: parser.y


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
%{ #include #include #include #include #include <stdlib.h> <stdio.h> "global.h" "util.h" "symtab.h"

int yylex(void); void yyerror(const char *, ...); int lineno; char * saved_name; %} %union{ struct token_t *token; struct node_t *node; } %token %token %token %token %token AND ATTR ELSE END EQ GE GT IF LE LPAREN LT ; MINUS NEQ OR OVER PLUS READ RPAREN SEMI TIMES ; WHILE WRITE; <val> NUM; <name> ID;

%type <node> stmts stmt while_decl if_decl attrib_decl write_decl read_decl; %type <node> bool expr factor; %left EQ NEQ; %left GE GT LE LT; %left TIMES OVER; %left PLUS MINUS; %left LPAREN; %nonassoc ATTR; %% program : stmts { ast = $1; } ; stmts : stmts stmt { struct node_t *t = $1; if (t != NULL)

37

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

{ while (t->next != NULL) t = t->next; t->next = $2; $$ = $1; } else $$ = $2; } | stmt { $$ = $1; } ; stmt : if_decl SEMI { $$ = $1; } | while_decl SEMI { $$ = $1; } | attrib_decl SEMI { $$ = $1; } | read_decl SEMI { $$ = $1; } | write_decl SEMI { $$ = $1; } ; : IF LPAREN bool RPAREN stmts END { $$ = new_stmt_node(if_k); $$->child[0] = $3; $$->child[1] = $5; } | IF LPAREN bool RPAREN stmts ELSE stmts END { $$ = new_stmt_node(if_k); $$->child[0] = $3; $$->child[1] = $5; $$->child[2] = $7; } ;

if_decl

while_decl : WHILE LPAREN bool RPAREN stmts END { $$ = new_stmt_node(while_k); $$->child[0] = $3; $$->child[1] = $5; } ; attrib_decl : ID { saved_name = copy_str ((yylval.token)->value.name); lineno = yylval.token->lineno; } ATTR expr { $$ = new_stmt_node(attrib_k); $$->child[0] = $4; $$->attr.name = saved_name; $$->lineno = lineno; symtab_insert(stab, saved_name); } ; read_decl : READ ID { $$ = new_stmt_node(read_k); $$->child[0] = new_expr_node(id_k); $$->child[0]->attr.name = copy_str ((yylval.token)->value.name); $$->lineno = yylval.token->lineno; symtab_insert(stab, (yylval.token)->value.name); } ; write_decl : WRITE ID { $$ = new_stmt_node(write_k); $$->child[0] = new_expr_node(id_k); $$->child[0]->attr.name = copy_str ((yylval.token)->value.name);

38

123 $$->lineno = yylval.token->lineno; 124 } 125 ; 126 127 expr : expr PLUS expr 128 { 129 $$ = new_expr_node(op_k); 130 $$->child[0] = $1; 131 $$->child[1] = $3; 132 $$->attr.op = PLUS; 133 } 134 | expr MINUS expr 135 { 136 $$ = new_expr_node(op_k); 137 $$->child[0] = $1; 138 $$->child[1] = $3; 139 $$->attr.op = MINUS; 140 } 141 | expr TIMES expr 142 { 143 $$ = new_expr_node(op_k); 144 $$->child[0] = $1; 145 $$->child[1] = $3; 146 $$->attr.op = TIMES; 147 } 148 | expr OVER expr 149 { 150 $$ = new_expr_node(op_k); 151 $$->child[0] = $1; 152 $$->child[1] = $3; 153 $$->attr.op = OVER; 154 } 155 | factor 156 { $$ = $1; } 157 | bool 158 { $$ = $1; } 159 ; 160 161 bool : expr OR expr 162 { 163 $$ = new_expr_node(op_k); 164 $$->child[0] = $1; 165 $$->child[1] = $3; 166 $$->attr.op = OR; 167 } 168 | expr AND expr 169 { 170 $$ = new_expr_node(op_k); 171 $$->child[0] = $1; 172 $$->child[1] = $3; 173 $$->attr.op = AND; 174 } 175 | expr EQ expr 176 { 177 $$ = new_expr_node(op_k); 178 $$->child[0] = $1; 179 $$->child[1] = $3; 180 $$->attr.op = EQ; 181 } 182 | expr NEQ expr 183 { 184 $$ = new_expr_node(op_k); 185 $$->child[0] = $1; 186 $$->child[1] = $3; 187 $$->attr.op = NEQ; 188 } 189 | expr GT expr 190 { 191 $$ = new_expr_node(op_k); 192 $$->child[0] = $1; 193 $$->child[1] = $3; 194 $$->attr.op = GT; 195 } 196 | expr LT expr 197 { 198 $$ = new_expr_node(op_k); 199 $$->child[0] = $1; 200 $$->child[1] = $3;

39

201 $$->attr.op = LT; 202 } 203 | expr GE expr 204 { 205 $$ = new_expr_node(op_k); 206 $$->child[0] = $1; 207 $$->child[1] = $3; 208 $$->attr.op = GE; 209 } 210 | expr LE expr 211 { 212 $$ = new_expr_node(op_k); 213 $$->child[0] = $1; 214 $$->child[1] = $3; 215 $$->attr.op = LE; 216 } 217 | expr 218 { $$ = $1; } 219 ; 220 221 factor : LPAREN expr RPAREN 222 { $$ = $2; } 223 | ID 224 { 225 struct symtab_t * symbol = NULL; 226 $$ = new_expr_node(id_k); 227 $$->attr.name = copy_str ((yylval.token)->value.name); 228 $$->lineno = yylval.token->lineno; 229 symbol = symtab_lookup(stab, (yylval.token)->value.name); 230 if ( symbol == NULL ) 231 { 232 fprintf(stderr,"Syntax error: Symbol %s not found, line %d.\n", (yylval.token)->value.name, yylval.
token->lineno);

233 exit(EXIT_FAILURE); 234 } 235 } 236 | NUM 237 { 238 $$ = new_expr_node(const_k); 239 $$->attr.val = (yylval.token)->value.val; 240 } 241 ; 242 %% 243 244 void yyerror(const char * s, ...) { 245 printf("syntax error: %s\n", s); 246 return; 247 }

Listagem 23: util.h


1 2 3 4 5 6 7 8 9 10 11 12
#ifndef _UTIL_H_ #define _UTIL_H_ struct node_t * new_expr_node (enum expr_kind kind); struct node_t * new_stmt_node (enum stmt_kind kind); struct token_t * new_token(void); void print_node (struct node_t * node); void print_tree (struct node_t * n); char *copy_str(char * str); #endif /* _UTIL_H_ */

Listagem 24: util.c


1 2 3 4 5 6 7 8
#include #include #include #include #include #include <stdlib.h> <stdio.h> <string.h> "global.h" "util.h" "compiler-parser.h"

static int indent_level = 0;

40

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

struct node_t * new_expr_node(enum expr_kind kind) { struct node_t * n = (struct node_t *) malloc (sizeof (struct node_t)); int i; for(i=0 ; i<3; i++) n->child[i] = NULL; n->next = NULL; n->lineno = 0; n->kind = expr_k; n->type.expr = kind; n->attr.name = NULL; return n; } struct node_t * new_stmt_node(enum stmt_kind kind) { struct node_t *n = (struct node_t *) malloc(sizeof(struct node_t)); int i; for(i=0 ; i<3; i++) n->child[i] = NULL; n->next = NULL; n->lineno = 0; n->kind = stmt_k; n->type.stmt = kind; return n; } struct token_t * new_token(void) { struct token_t * t = (struct token_t *) malloc (sizeof (struct token_t)); return t; } void print_node(struct node_t * node) { int i; for (i = 0 ; i < indent_level ; i++) fprintf(stderr, " "); if (node == NULL) return; switch (node->kind) { case stmt_k: fprintf(stderr, "Stament: "); switch (node->type.stmt) { case if_k: fprintf(stderr, "IF\n"); break; case write_k: fprintf(stderr, "WRITE\n"); break; case attrib_k: fprintf(stderr, "ATTRIB to %s\n", node->attr.name); break; case while_k: fprintf(stderr, "WHILE\n"); break; case read_k: fprintf(stderr, "READ\n"); break; } break; case expr_k: fprintf(stderr, "Expression: "); switch (node->type.expr) { case id_k: fprintf(stderr, "ID: %s\n", node->attr.name); break; case op_k: fprintf(stderr, "OP: "); switch (node->attr.op) { case AND: fprintf(stderr, "&&"); break; case OR: fprintf(stderr, "||"); break; case EQ:

41

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

"/"); break; } fprintf(stderr, "\n"); break; case const_k: fprintf(stderr, "NUM: %d\n", node->attr.val); break; } break; } return; } char * copy_str (char * str) { char * aux; int len = 0; len = strlen(str) + 1; aux = (char *) malloc (len); memset(aux, 0, len); memcpy(aux, str, len - 1); return aux; } void print_tree (struct node_t * node) { static int first = 1; int i = 0; if (first){ fprintf (stderr, "\n======== Printing AST ========\n"); first = 0; } if (node != NULL) { print_node(node); for (i = 0 ; i < MAX_CHILDREN ; i++) if (node->child[i]) { indent_level++; print_tree(node->child[i]); indent_level--; } if (node->next != NULL) print_tree(node->next); } return; }

fprintf(stderr, case NEQ: fprintf(stderr, case GE: fprintf(stderr, case GT: fprintf(stderr, case LE: fprintf(stderr, case LT: fprintf(stderr, case PLUS: fprintf(stderr, case MINUS: fprintf(stderr, case TIMES: fprintf(stderr, case OVER: fprintf(stderr,

"=="); break; "!="); break; ">="); break; ">"); break; "<="); break; "<"); break; "+"); break; "-"); break; "*"); break;

Listagem 25: symtab.h


1 2 3 4 5 6 7
#ifndef _SYMTAB_H #define _SYMTAB_H #define HASH_TABLE_SIZE 1117 #define VAR_LOCATIONS 512 struct locations {

42

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

int locations[VAR_LOCATIONS + 1]; }; struct symtab_t { char * id; struct locations l; struct symtab_t * next; };

unsigned int hash (char * id); struct symtab_t ** symtab_new(void); int symtab_insert (struct symtab_t ** tab, char * id); struct symtab_t * symtab_lookup (struct symtab_t ** tab, char * id); int symtab_destroy(struct symtab_t ** tab); void symtab_print(struct symtab_t ** tab);

#endif /* _SYMTAB_H */

Listagem 26: symtab.c


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
#include #include #include #include "symtab.h" <stdlib.h> <stdio.h> <string.h>

unsigned int weight_array[HASH_TABLE_SIZE + 1]; void generate_weights(void) { int i; for (i =0 ; i < HASH_TABLE_SIZE ; i++ ) weight_array[i] = rand(); weight_array[HASH_TABLE_SIZE + 1] = 0; return; } unsigned int hash(char * id) { int len, i, j=0; unsigned int h, temp=0; len = strlen(id); for (i = 0; i < len; i++) { if (j > (HASH_TABLE_SIZE - 1)) j = 0; temp = temp + id[i] * weight_array[j]; j++; } h = temp % HASH_TABLE_SIZE; return h; } struct symtab_t ** symtab_new(void) { int i; struct symtab_t ** tab; generate_weights(); tab = (struct symtab_t **) malloc (sizeof (struct symtab_t *) * (HASH_TABLE_SIZE + 1)); *(tab + HASH_TABLE_SIZE + 1) = NULL; for (i = 0; i < HASH_TABLE_SIZE ; i++) { *(tab + i) = (struct symtab_t *) malloc (sizeof (struct symtab_t)); (**(tab + i)).id = NULL; } return tab; } int symtab_insert (struct symtab_t ** tab, char * id) { struct symtab_t * entry; unsigned int h; if (id == NULL) return -1; if ((entry = symtab_lookup (tab, id)) == NULL)

43

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

{ h = hash (id); if ( (**(tab + h)).id == 0x0) { (**(tab + h)).id = strdup(id); } else { entry = *(tab + h); while (entry->next != NULL) entry = entry->next; entry->next = (struct symtab_t *) malloc (sizeof (struct symtab_t)); entry->next->id = strdup (id); } return 1; } return 0; } struct symtab_t * symtab_lookup (struct symtab_t ** tab, char * id) { struct symtab_t * entry; unsigned int h; if (id == NULL) return NULL; h = hash(id); if (((**(tab+h)).id) && (strcmp ( (**(tab + h)).id, id) == 0)) return *(tab + h); else { entry = *(tab + h); while (entry->next != NULL) { entry = entry->next; if (strcmp (entry->id, id) == 0) return entry; } } return NULL; } int symtab_destroy (struct symtab_t ** tab) { return 0; } void symtab_print(struct symtab_t ** tab) { int i; struct symtab_t * entry; fprintf(stderr, "\n======== Printing Symtab ========\n"); for (i = 0; i < HASH_TABLE_SIZE; i++) { entry = *(tab + i); if (entry->id && (strlen(entry->id) != 0)) { fprintf(stderr, "Entry %d:\n", i); fprintf(stderr, "IDs: %s", entry->id); while (entry->next != NULL) { entry = entry->next; fprintf(stderr, ", %s", entry->id); } fprintf(stderr, "\n"); } } return; }

Listagem 27: cgen.h


1 #ifndef _CGEN_H 2 #define _CGEN_H 3

44

4 void generate_c (FILE * cfile, struct node_t * ast, struct symtab_t ** stab); 5 6 #endif /* _CGEN_H */

Listagem 28: cgen.c


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
#include #include #include #include #include #include void void void void void void void void void void <stdlib.h> <stdio.h> "global.h" "compiler-parser.h" "symtab.h" "cgen.h"

declare_variables (FILE * cfile, struct symtab_t ** stab); gen_c (FILE * cfile, struct node_t * ast); emit_op (FILE * cfile, struct node_t * node); emit_const (FILE * cfile, struct node_t * node); emit_id (FILE * cfile, struct node_t * node); emit_if (FILE * cfile, struct node_t * node); emit_while (FILE * cfile, struct node_t * node); emit_attrib (FILE * cfile, struct node_t * node); emit_read (FILE * cfile, struct node_t * node); emit_write (FILE * cfile, struct node_t * node);

void generate_c (FILE * cfile, struct node_t * ast, struct symtab_t ** stab) { fprintf (cfile, "#include <unistd.h>\n"); fprintf (cfile, "#include <stdlib.h>\n"); fprintf (cfile, "#include <stdio.h>\n"); fprintf (cfile, "int main (int argc, char **argv)\n"); fprintf (cfile, "{\n"); declare_variables (cfile, stab); gen_c (cfile, ast); fprintf (cfile, "exit(EXIT_SUCCESS)\n"); fprintf (cfile, "}\n"); return; } void declare_variables (FILE * cfile, struct symtab_t ** stab) { int i; struct symtab_t * entry; fprintf (cfile, " int"); for (i = 0; i < HASH_TABLE_SIZE ; i++) { static int first = 1; entry = *(stab + i); if (entry != NULL && entry->id != NULL) if (first) { fprintf (cfile, " %s", entry->id); first = 0; } else fprintf (cfile, ", %s", entry->id); } fprintf (cfile, ";\n"); return; } void gen_c (FILE * cfile, struct node_t * ast) { struct node_t * node; node = ast; if (node != NULL) { switch (node->kind) { case stmt_k: switch (node->type.stmt) { case if_k: emit_if (cfile, node); break; case while_k:

45

71 emit_while (cfile, node); 72 break; 73 case attrib_k: 74 emit_attrib (cfile, node); 75 break; 76 case read_k: 77 emit_read (cfile, node); 78 break; 79 case write_k: 80 emit_write (cfile, node); 81 break; 82 } 83 break; 84 case expr_k: 85 switch (node->type.expr) 86 { 87 case op_k: 88 emit_op (cfile, node); 89 break; 90 case id_k: 91 emit_id (cfile, node); 92 break; 93 case const_k: 94 emit_const (cfile, node); 95 break; 96 } 97 break; 98 default: 99 fprintf (stderr, "This shouldnt happended. You probabily found BUG."); 100 exit (EXIT_FAILURE); 101 } 102 gen_c(cfile, node->next); 103 } 104 } 105 106 void emit_op (FILE * cfile, struct node_t * node) 107 { 108 gen_c (cfile, node->child[0]); 109 switch (node->attr.op) 110 { 111 case AND: 112 fprintf (cfile, " && "); 113 break; 114 case OR: 115 fprintf (cfile, " || "); 116 break; 117 case EQ: 118 fprintf (cfile, " == "); 119 break; 120 case NEQ: 121 fprintf (cfile, " != "); 122 break; 123 case GE: 124 fprintf (cfile, " >= "); 125 break; 126 case GT: 127 fprintf (cfile, " > "); 128 break; 129 case LE: 130 fprintf (cfile, " <= "); 131 break; 132 case LT: 133 fprintf (cfile, " < "); 134 break; 135 case PLUS: 136 fprintf (cfile, " + "); 137 break; 138 case MINUS: 139 fprintf (cfile, " - "); 140 break; 141 case TIMES: 142 fprintf (cfile, " * "); 143 break; 144 case OVER: 145 fprintf (cfile, " / "); 146 break; 147 } 148 gen_c (cfile, node->child[1]);

46

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

return; } void emit_id (FILE * cfile, struct node_t * node) { fprintf (cfile, "%s", node->attr.name); return; } void emit_const (FILE * cfile, struct node_t * node) { fprintf (cfile, "%d", node->attr.val); return; } void emit_if (FILE * cfile, struct node_t * node) { fprintf (cfile, "if ("); gen_c (cfile, node->child[0]); fprintf (cfile, ")\n{\n"); gen_c (cfile, node->child[1]); fprintf (cfile, "}\n"); if (node->child[2]) { fprintf (cfile, "else\n"); fprintf (cfile, "{\n"); gen_c (cfile, node->child[2]); fprintf (cfile, "}\n"); } return; } void emit_while (FILE * cfile, struct node_t * node) { fprintf (cfile, "while ("); gen_c (cfile, node->child[0]); fprintf (cfile, ")\n{\n"); gen_c (cfile, node->child[1]); fprintf (cfile, "}\n"); return; } void emit_attrib (FILE * cfile, struct node_t * node) { fprintf (cfile, "%s = ", node->attr.name); gen_c (cfile, node->child[0]); fprintf (cfile, ";\n"); } void emit_read (FILE * cfile, struct node_t * node) { fprintf (cfile, "scanf(\"%%d\", &"); gen_c (cfile, node->child[0]); fprintf (cfile, ");\n"); return; } void emit_write (FILE * cfile, struct node_t * node) { fprintf (cfile, "printf(\"%%d\", "); gen_c (cfile, node->child[0]); fprintf (cfile, ");\n"); return; }

Listagem 29: dotgen.h


1 2 3 4 5 6 7 8 9
#ifndef _DOTGEN_H_ #define _DOTGEN_H_ #include <stdio.h> #include "global.h" void generate_dot (FILE * file, struct node_t * ast); #endif /* _DOTGEN_H_ */

47

Listagem 30: dotgen.c


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
#include #include #include #include #include #include char void void void void void void void <stdlib.h> <stdio.h> <string.h> "global.h" "dotgen.h" "compiler-parser.h"

* gen_name(struct node_t *node); dot_emit_preamble (FILE * file); dot_gen_labels (FILE * file, struct node_t * node); dot_gen_graph (FILE * file, struct node_t * node, struct node_t * context); dot_gen_shapes (FILE * file, struct node_t * node); dot_emit_finally (FILE * file); dot_emit_if (FILE * file, struct node_t * node, struct node_t * context); dot_emit_while (FILE * file, struct node_t * node, struct node_t * context);

void generate_dot (FILE * file, struct node_t * ast) { dot_emit_preamble(file); dot_gen_labels (file, ast); dot_gen_graph (file, ast, NULL); dot_gen_shapes (file, ast); dot_emit_finally (file); } void dot_emit_preamble (FILE * file) { fprintf (file, "digraph program {\n"); fprintf (file, "node [shape=box];\n"); return; } void dot_emit_finally (FILE * file) { fprintf (file, "}\n"); return; } void dot_print_op(FILE * file, int kind) { switch (kind) { case PLUS: fprintf(file, " + "); break; case MINUS: fprintf (file, " - "); break; case TIMES: fprintf (file, " * "); break; case OVER: fprintf (file, " / "); break; case EQ: fprintf (file, " == "); break; case NEQ: fprintf (file, " != "); break; case AND: fprintf (file, " && "); break; case OR: fprintf (file, " || "); break; case GE: fprintf (file, " >= "); break; case GT: fprintf (file, " > "); break; case LE: fprintf (file, " <= "); break; case LT:

48

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

fprintf (file, " < "); break; default: fprintf(file, " "); break; } return; } void dot_gen_if_label(FILE * file, struct node_t * node) { char * name; if (node && node->kind == stmt_k && node->type.stmt == if_k) { name = gen_name (node); fprintf (file, "%s [label = \" ", name); dot_gen_labels (file, node->child[0]); fprintf (file, "\" ];\n"); free (name); dot_gen_labels (file, node->child[1]); dot_gen_labels (file, node->child[2]); } return; } void dot_gen_while_label(FILE * file, struct node_t * node) { char * name; if (node && node->kind == stmt_k && node->type.stmt == while_k) { name = gen_name (node); fprintf (file, "%s [label = \" ", name); dot_gen_labels (file, node->child[0]); fprintf (file, "\" ];\n"); free (name); dot_gen_labels (file, node->child[1]); } return; } void dot_gen_labels (FILE * file, struct node_t * node) { char *name; if (node) { switch (node->kind) { case stmt_k: switch (node->type.stmt) { case if_k: dot_gen_if_label(file, node); break; case while_k: dot_gen_while_label(file, node); break; case attrib_k: name = gen_name (node); fprintf (file, "%s [label = \"", name); fprintf (file, "%s = ", node->attr.name); dot_gen_labels (file, node->child[0]); fprintf (file, "\" ];\n"); free (name); break; case read_k: name = gen_name (node); fprintf (file, "%s [label = \"", name); fprintf(file, "leia "); dot_gen_labels (file, node->child[0]); fprintf (file, "\" ];\n"); free (name); break; case write_k: name = gen_name (node); fprintf (file, "%s [label = \"", name); fprintf(file, "escreva ");

49

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

dot_gen_labels (file, node->child[0]); fprintf (file, "\" ];\n"); free (name); break; } break; case expr_k: switch (node->type.expr) { case const_k: fprintf (file, "%d", node->attr.val); break; case op_k: dot_gen_labels (file, node->child[0]); dot_print_op(file, node->attr.op); dot_gen_labels (file, node->child[1]); break; case id_k: fprintf (file, "%s", node->attr.name); break; } break; } dot_gen_labels (file, node->next); } } void dot_gen_graph (FILE * file, struct node_t * node, struct node_t * context) { char * name, * next; if (node && node->kind == stmt_k) { switch (node->type.stmt) { case if_k: dot_emit_if (file, node, context); break; case while_k: dot_emit_while (file, node, context); break; case read_k: case write_k: case attrib_k: name = gen_name (node); fprintf (file, "%s", name); if (node->next != NULL) { next = gen_name (node->next); fprintf (file, " -> %s", next); free (next); } else { if (context != NULL) { next = gen_name (context); fprintf (file, " -> %s", next); free (next); } } fprintf(file, ";\n"); free (name); dot_gen_graph (file, node->next, context); break; default: fprintf(stderr, "BUG\n"); exit(EXIT_FAILURE); } } return; } void dot_gen_shapes (FILE * file, struct node_t * node) { if (node && node->kind == stmt_k) { char * name; int i = 0; name = gen_name (node);

50

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

switch (node->type.stmt) { case if_k: fprintf (file, "%s [shape=diamond];", name); break; case while_k: fprintf (file, "%s [shape=diamond];", name); break; case read_k: case write_k: case attrib_k: /* do nothing */ break; default: fprintf(stderr, "BUG\n"); exit(EXIT_FAILURE); } free(name); for (i = 0; i < MAX_CHILDREN; i++) dot_gen_shapes (file, node->child[i]); dot_gen_shapes (file, node->next); } return; } void dot_emit_if (FILE * file, struct node_t * node, struct node_t * context) { char *node_name, *child_name; node_name = gen_name (node); child_name = gen_name (node->child[1]); fprintf(file, "%s -> %s [label =\"true\"];\n", node_name, child_name); dot_gen_graph (file, node->child[1], (node->next != NULL ? node->next : context)); if (node->child[2] != NULL) { free (child_name); child_name = gen_name (node->child[2]); fprintf(file, "%s -> %s [label =\"false\"];\n", node_name, child_name); dot_gen_graph (file, node->child[2], (node->next != NULL ? node->next : context)); } else { char * next; if (node->next != NULL) next = gen_name (node->next); else next = gen_name (context); fprintf (file, "%s -> %s [label =\"false\"];\n", node_name, next); free (next); } free (node_name); free (child_name); dot_gen_graph (file, node->next, context); return; } void dot_emit_while (FILE * file, struct node_t * node, struct node_t * context) { char *node_name, *child_name; char * next; node_name = gen_name (node); child_name = gen_name (node->child[1]); fprintf(file, "%s -> %s [label =\"true\"];\n", node_name, child_name); dot_gen_graph (file, node->child[1], node); if (node->next) next = gen_name (node->next); else next = gen_name (context); fprintf (file, "%s -> %s [label =\"false\"];\n", node_name, next); free (next); free (node_name); free (child_name); dot_gen_graph (file, node->next, context); return; } char * gen_name(struct node_t * node) { char * str; str = (char *) malloc (sizeof(char) * 33); memset (str, 0, 33); switch (node->type.stmt) {

51

311 case if_k: 312 sprintf(str, "if%d", (int) node); 313 break; 314 case while_k: 315 sprintf(str, "while%d", (int) node); 316 break; 317 case read_k: 318 sprintf(str, "read%d", (int) node); 319 break; 320 case write_k: 321 sprintf(str, "write%d", (int) node); 322 break; 323 case attrib_k: 324 sprintf(str, "attrib%d", (int) node); 325 break; 326 default: 327 fprintf(stderr, "BUG\n"); 328 exit(EXIT_FAILURE); 329 } 330 return str; 331 }

Você também pode gostar