• Nenhum resultado encontrado

Compilador para linguagem reversível Janus

N/A
N/A
Protected

Academic year: 2021

Share "Compilador para linguagem reversível Janus"

Copied!
44
0
0

Texto

(1)

Universidade Federal Fluminense

Instituto de Computa¸

ao

Departamento de Ciˆ

encia da Computa¸

ao

Vin´ıcius de Carvalho Brum

COMPILADOR PARA LINGUAGEM

REVERS´IVEL JANUS

Niteroi-RJ

2017

(2)

ii VIN´ICIUS DE CARVALHO BRUM

COMPILADOR PARA LINGUAGEM REVERS´IVEL JANUS

Trabalho submetido ao Curso de

Bacharelado em Ciˆencia da Computa¸c˜ao da Universidade Federal Fluminense como requisito parcial para a obten¸c˜ao do t´ıtulo de Bacharel em Ciˆencia da Computa¸c˜ao.

Orientador: Prof. Luis Antonio Brasil Kowada

Niteroi-RJ 2017

(3)

Ficha Catalográfica elaborada pela Biblioteca da Escola de Engenharia e Instituto de Computação da UFF

B893 Brum, Vinícius de Carvalho

Compilador para linguagem reversível Janus / Vinícius de Carvalho Brum. – Niterói, RJ : [s.n.], 2017.

42 f.

Projeto Final (Bacharelado em Ciência da Computação) – Universidade Federal Fluminense, 2017.

Orientador: Luis Antonio Brasil Kowada.

1. Compilador (Programa de computador). 2. Linguagem reversível. I. Título.

CDD 005.453

(4)
(5)

iv

Dedico este trabalho aos meus pais e irm˜ao, pelo apoio incondicional em todos os momen-tos.

(6)

v

Agradecimentos

Agrade¸co aos meus pais, irm˜ao por todo apoio que sempre me deram. Ao meu orientador, pela ajuda na confec¸c˜ao deste trabalho. Aos meus colegas e professores que ajudaram a caminhar na minha gradua¸c˜ao. Aos professores Aline e Bruno pela presen¸ca na banca examinadora.

(7)

vi

Resumo

Uma linguagem de programa¸c˜ao ´e revers´ıvel se todos os seus comandos podem ser executados na ordem reversa. Isto significa que um programa escrito em tal linguagem ´e capaz de ser revertido para qualquer ponto e ser executado novamente, toda mudan¸ca de estado de um programa pode ser desfeita. Este trabalho tem como prop´osito desenvolver um compilador para a linguagem revers´ıvel Janus. O compilador foi feito em Java, seus Analisadores L´exico e Sint´atico foram gerados a partir das bibliotecas JFlex e Java Cup. Palavras-chave: Compilador, Linguagem revers´ıvel

(8)

vii

Abstract

A command is reversible if it can be undone. A programming language is reversible if all of its commands are reversible and the control flow can be executed in inverse order. The goal of this work is develop a Compiler for Janus (a reversible programming language). This compiler was written in Java Language, using JFlex and Java Cup tools for generate Lexical and Syntactic Analyzers.

(9)

Sum´

ario

Resumo vi Abstract vii Lista de Tabelas xi 1 Introdu¸c˜ao 1 1.1 Compiladores . . . 1

1.2 Linguagem revers´ıvel Janus . . . 2

1.3 Objetivo . . . 2

1.4 Estrutura deste trabalho . . . 2

2 Compilador 4 2.1 Etapa de an´alise . . . 4

2.1.1 An´alise l´exica . . . 4

2.1.2 An´alise sint´atica . . . 7

2.1.3 An´alise semˆantica . . . 11

2.2 Etapa de s´ıntese do c´odigo . . . 12

2.2.1 Gera¸c˜ao de c´odigo intermedi´ario . . . 13

2.2.2 Otimiza¸c˜ao do c´odigo . . . 15

2.2.3 Gera¸c˜ao do c´odigo final . . . 16

3 A Linguagem Janus 19 3.1 Gram´atica . . . 20

3.2 Reverso de Instru¸c˜oes . . . 23

3.3 Estruturas de Controle . . . 24

3.3.1 Estrutura Condicional . . . 24

(10)

ix

3.3.2 Estrutura Iterativa . . . 25

4 Compilador para Linguagem Janus 26 4.1 Gerador do Analisador L´exico . . . 26

4.2 Gerador do Analisador Sint´atico . . . 26

4.3 Analisador Semˆantico . . . 27

4.3.1 Padr˜ao de projeto Visitor . . . 27

4.3.2 Tabela de s´ımbolos . . . 27

4.3.3 Verifica¸c˜ao de erros . . . 28

4.4 Gera¸c˜ao de c´odigo . . . 29

4.4.1 Representa¸c˜ao Intermedi´aria . . . 29

4.4.2 Assembly MIPS . . . 30

5 Conclus˜ao 31

(11)

Lista de Tabelas

2.1 Exemplos de tokens e lexemas . . . 5

2.2 Categorias e Tokens . . . 7

2.3 Regras de Produ¸c˜ao . . . 9

2.4 Passos do Algoritmo Empilha-Reduz . . . 10

2.5 Tipos de erros semˆanticos . . . 12

2.6 Nota¸c˜oes P´os-fixa e Infixa . . . 13

2.7 Instru¸c˜oes de Atribui¸c˜ao . . . 14

2.8 Instru¸c˜oes de Desvio . . . 14

2.9 Instru¸c˜oes de Invoca¸c˜ao de Rotina . . . 14

2.10 Instru¸c˜oes de Acesso Indexado . . . 14

2.11 Simplifica¸c˜ao alg´ebrica . . . 15

2.12 Elimina¸c˜ao de subexpress˜ao comum . . . 15

2.13 Propaga¸c˜ao de c´opia . . . 16

2.14 Elimina¸c˜ao de c´odigo morto . . . 16

2.15 Registradores MIPS . . . 17

2.16 Algumas instru¸c˜oes em Assembly MIPS . . . 18

3.1 Operadores aritm´eticos bin´arios . . . 21

3.2 Operadores l´ogicos bin´arios . . . 22

3.3 Operadores relacionais bin´arios . . . 22

3.4 Operadores un´arios . . . 22

3.5 Operadores de Modifica¸c˜ao e Swap . . . 23

3.6 Reverso de instru¸c˜oes . . . 23

3.7 Estrutura Condicional . . . 24

3.8 Estrutura Iterativa . . . 25

(12)

xi 4.1 Erros semˆanticos . . . 28 4.2 Instru¸c˜oes quadruplas . . . 29

(13)

Cap´ıtulo 1

Introdu¸

ao

1.1

Compiladores

O compilador ´e um software que lˆe um c´odigo-fonte escrito numa determinada linguagem de programa¸c˜ao e o traduz para outro c´odigo que esteja escrito em outra linguagem ou em uma linguagem de m´aquina [4]. O caso mais comum ´e a convers˜ao de um c´odigo em linguagem de programa¸c˜ao de alto n´ıvel para um c´odigo em linguagem de baixo n´ıvel ou linguagem de m´aquina. O c´odigo produzido pelo processo de compila¸c˜ao ´e chamado de c´odigo-objeto.

O compilador pode ser dividido em duas partes: a de an´alise e a de s´ıntese. A parte de an´alise ´e constitu´ıda pelas an´alises l´exica, sint´atica e semˆantica e a parte de s´ıntese ´e formada pela gera¸c˜ao de c´odigo intermedi´ario, otimiza¸c˜ao do c´odigo e a gera¸c˜ao do c´ odigo-objeto. A parte de an´alise e a de s´ıntese tamb´em podem ser chamadas, respectivamente, de front-end e back-end. A compila¸c˜ao come¸ca com o Analisador L´exico (tamb´em cha-mado de Scanner), que lˆe o c´odigo-fonte, eliminando coment´arios e espa¸cos desnecess´arios e separando e classificando os termos numa sequˆencia de unidades significativas chamadas de tokens. Ap´os esta fase, ´e a vez do Analisador Sint´atico (tamb´em chamado de Par-ser), que tem como objetivo avaliar se a sequˆencia de tokens est´a organizada obedecendo uma gram´atica formal associada `a linguagem. O processo de an´alise sint´atica produz uma ´arvore de acordo com a aplica¸c˜ao desta gram´atica, denominada ´arvore sint´atica. E encerrando o processo de an´alise, o Analisador Semˆantico entra em a¸c˜ao para garantir se as regras semˆanticas est˜ao sendo respeitadas, como por exemplo, se as vari´aveis que est˜ao sendo utilizadas foram declaradas, se os tipos de dados s˜ao compat´ıveis com a

(14)

2 ra¸c˜ao, se n˜ao tem diferentes vari´aveis com o mesmo identificador e etc. Ap´os a an´alise da corretude do c´odigo-fonte, ´e feita a gera¸c˜ao do c´odigo-objeto da linguagem alvo, sendo que comumente, os compiladores geram um c´odigo intermedi´ario antes disso. H´a tamb´em uma etapa de otimiza¸c˜ao, de forma que o programa possa ser executado mais rapidamente ou ocupe menos espa¸co. Este processo pode ser feito em qualquer etapa da compila¸c˜ao, inclusive na etapa de an´alise, mas costuma ser aplicado no c´odigo intermedi´ario ou no c´odigo-objeto [4].

1.2

Linguagem revers´ıvel Janus

A linguagem revers´ıvel Janus, proposta por Lutz e Derby [5] e descrita de forma mais completa por [7], ´e uma linguagem simples, por´em poderosa o suficiente para de-senvolver algoritmos complexos [6] e suas constru¸c˜oes podem servir como modelo para o desenvolvimento de outras linguagens revers´ıveis. Todas as instru¸c˜oes aceitas por sua estrutura tˆem a sua vers˜ao reversa (instru¸c˜oes capazes de recuperar um estado anterior do programa revers´ıvel) o que classifica esta linguagem como revers´ıvel, diferente das lin-guagens tradicionais que s˜ao uma combina¸c˜ao de constru¸c˜oes revers´ıveis e irrevers´ıveis. Plataformas revers´ıveis podem prover uma execu¸c˜ao revers´ıvel para um programa irre-vers´ıvel, mas isso seria muito custoso (sobrecarga na mem´oria e aumento do tempo de execu¸c˜ao). Caso o mesmo programa fosse escrito numa linguagem revers´ıvel sua execu¸c˜ao seria menos custosa [6].

1.3

Objetivo

Este trabalho tem como objetivo desenvolver um compilador para linguagem re-vers´ıvel Janus, que inclui a implementa¸c˜ao de todas as etapas da fase de an´alise: an´alises l´exica, sint´atica e semˆantica, e tamb´em, as etapas da fase de s´ıntese: gera¸c˜ao de c´odigo intermedi´ario e gera¸c˜ao do c´odigo final.

1.4

Estrutura deste trabalho

No Cap´ıtulo 2 as etapas de an´alise e s´ıntese s˜ao explicadas detalhamente. As fun-¸c˜oes, os erros e exemplos sobre as an´alises l´exicas, sint´atica e semˆantica s˜ao apresentadas

(15)

3 na parte de an´alise. Os detalhes sobre gera¸c˜ao do c´odigo intermedi´ario, sua otimiza¸c˜ao e a gera¸c˜ao do c´odigo final s˜ao mostradas na parte de s´ıntese. No Cap´ıtulo 3 s˜ao apresentados a fundo aspectos importantes da linguagem Janus, desde a sua gram´atica at´e suas estru-turas e instru¸c˜oes reversas. No Cap´ıtulo 4 s˜ao descritos detalhes sobre a implementa¸c˜ao do compilador para a linguagem revers´ıvel Janus, desde a an´alise l´exica at´e a gera¸c˜ao de c´odigo. No Cap´ıtulo 5 s˜ao apresentadas as conclus˜oes deste trabalho.

(16)

Cap´ıtulo 2

Compilador

Neste cap´ıtulo ser´a apresentado o processo de compila¸c˜ao que ´e dividido em duas fases: an´alise e s´ıntese. A fase de an´alise possui trˆes etapas: an´alise l´exica, an´alise sint´atica e an´alise semˆantica. Nas primeiras se¸c˜oes, caracter´ısticas de cada uma das an´alises s˜ao descritas, como fun¸c˜oes, erros e exemplos. Ap´os as se¸c˜oes de an´alise, temos as de s´ıntese que tem o foco na gera¸c˜ao do c´odigo, nela s˜ao encontrados os t´opicos: gera¸c˜ao de c´odigo intermedi´ario, otimiza¸c˜ao do c´odigo e gera¸c˜ao do c´odigo final.

2.1

Etapa de an´

alise

Nesta se¸c˜ao ser˜ao apresentadas as etapas que comp˜oem a primeira fase do processo de compila¸c˜ao, partindo da an´alise l´exica e terminando na an´alise semˆantica.

2.1.1

An´

alise l´

exica

Dentre as fases do processo de compila¸c˜ao, o Analisador L´exico (ou Scanner) ´e a primeira fase. O seu objetivo ´e simples: ler os caracteres do arquivo-fonte, agrupando-os em unidades significativas de acordo com padr˜oes pr´e-definidos e descartando caracteres que n˜ao s˜ao relevantes para a linguagem alvo, como por exemplo, coment´arios, espa¸cos, caracteres de fim de linha ou fim de arquivo entre outros. Esta sequˆencia de caracte-res agrupados segundo algum padr˜ao ´e chamada de lexema. Cada padr˜ao determina uma categoria usada pelo Analisador Sint´atico. Por isso, o Analisador L´exico poderia trabalhar como uma sub-rotina ou co-rotina do Parser, passando para o mesmo o par hcategoria, lexemai, denominado token. Se o Analisador L´exico for executado antes do

(17)

5 Analisador Sint´atico, ele transforma o c´odigo-fonte numa sequˆencia de tokens, para ser lida pelo Analisador Sint´atico. Alguns tokens est˜ao associados a um ´unico lexema. De acordo com a tabela 2.1, por exemplo, ‘+’ est´a associado `a categoria PLUS, formando o token hPLUS, +i. Para outros tokens, h´a v´arios lexemas que satisfazem o padr˜ao da categoria, por exemplo, ‘123’ e ‘234’ ambas sequˆencias satisfazem a categoria num.

Categoria Padr˜ao Lexema Token

if Sequˆencia de caracteres for-mada por i,f

If, iF, IF, if hif , if i

id Sequˆencia de caracteres e digitos num´ericos que segue a express˜ao regular: [a-zA-Z][a-zA-Z0-9]*

A,Aa,aA,AA,aa,Aab,... hid, Ai, hid, Aai

for Sequˆencia de caracteres for-mada por f,o,r

For,FOr,FOR, foR, fOR, fOr, for, FoR

hfor, f ori

num Sequˆencia de d´ıgitos num´ e-ricos que segue a express˜ao regular: [0-9]+

1, 12, 123, 234,... hnum, 12i,

hnum, 234i

PLUS [+] + hPLUS, +i

Tabela 2.1: Exemplos de tokens e lexemas

H´a sequˆencias que satisfazem padr˜oes de categorias diferentes. Por exemplo, a sequˆencia ‘if’, na tabela 2.1 pode estar associada ao token hif , if i ou ao token hid, if i. Ou-tro tipo de ambiguidade na cria¸c˜ao dos tokens ´e quanto ao t´ermino do lexema. Por exem-plo, a sequˆencia de entrada ‘123’, pode ser classificada como {hnum, 123i} ou {hnum, 12i, hnum, 3i}. Para resolver estas ambiguidades, o Analisador L´exico precisa usar regras de escolha na produ¸c˜ao dos tokens. Por exemplo, dar preferˆencia a classificar um lexema como uma palavra reservada em vez de identificador (como ´e o caso do if), e tentar mon-tar o lexema com o maior tamanho dentre as poss´ıveis categorias (como ´e o caso do n´umero 123).

(18)

6 2.1.1.1 Erros l´exicos

Ao tentar montar a sequˆencia de tokens, h´a lexemas que podem n˜ao satisfazer nenhum padr˜ao. Por exemplo, a sequˆencia ‘123ab’ pode n˜ao corresponder a nenhuma categoria. Nesta situa¸c˜ao, o Analisador L´exico informa o tipo de erro e onde este caractere se encontra.

Mas o Analisador L´exico n˜ao consegue encontrar erros de digita¸c˜ao, como por exemplo: whyle(x == 3). O correto nesse caso seria ‘while’, mas como nesta etapa, cada sequˆencia de caracteres ´e classificada independentemente dos outros lexemas, esta sequˆencia ‘whyle’ formaria o token hid, whylei.

2.1.1.2 Exemplo de an´alise l´exica

Para ilustrar como funciona a an´alise l´exica, mostramos um exemplo de programa (Algoritmo 1) e em seguida, como pode ser feita a an´alise l´exica.

Algoritmo 1 Um exemplo de programa x = 10

while x 6= 0 do x = x − 1 end while

A entrada do Analisador L´exico ´e dividida em unidades e pode ser demonstrada no seguinte exemplo:

|x| |=| |10| \n |while| |x| |6=| |0| |do| \n |x| |=| |x| |-| |1| \n |end| |while| |<EOF>|

As cadeias de caracteres encontradas no c´odigo-fonte s˜ao avaliadas para verificar com qual padr˜ao se encaixam. Caso n˜ao combine com nenhum padr˜ao, a cadeia n˜ao ´e reconhecida e um erro ocorre. Na tabela 2.2 s˜ao apresentados os tokens de acordo com a sua categoria.

(19)

7

Categoria Padr˜ao Token

atrib “=” hatrib, =i

while “while” hwhile, whilei

do “do” hdo, doi

end “end” hend, endi

operadorDif “6=” hoperadorDif,6=i

operadorSub “-” hoperadorSub, −i

id [a-zA-Z][a-zA-Z0-9]* hid, xi

for “for” hfor, f ori

num [0-9]+ hnum, 10i,

hnum, 1i, hnum, 0i Tabela 2.2: Categorias e Tokens

2.1.2

An´

alise sint´

atica

A segunda fase do processo de compila¸c˜ao ´e realizada pelo Analisador Sint´atico (ou Parser ). O papel deste analisador ´e: avaliar se a sequˆencia de tokens recebida do Scanner pode ser gerada atrav´es da gram´atica formal da linguagem alvo e construir uma ´

arvore gramatical correspondente a esta sequˆencia.

A gram´atica possui um conjunto de regras que define a estrutura da linguagem. Estas regras s˜ao chamadas de regras de produ¸c˜ao (ou apenas de produ¸c˜ao) e s˜ao regras de reescrita que possibilitam a substitui¸c˜ao de s´ımbolos para gerar novas sequˆencias. Estas regras s˜ao compostas por dois tipos de s´ımbolos: os terminais, que s˜ao caracteres literais que n˜ao podem ser substitu´ıdos, e os n˜ao terminais, que podem ser substitu´ıdos. O uso destas regras gera cadeias de caracteres, este processo ´e denominado deriva¸c˜ao. A deriva¸c˜ao ´e uma opera¸c˜ao de substitui¸c˜ao de um s´ımbolo n˜ao terminal por s´ımbolos terminais e n˜ao terminais, os s´ımbolos que ocupam a posi¸c˜ao do n˜ao terminal fazem parte de uma regra de produ¸c˜ao que possui o s´ımbolo n˜ao terminal `a esquerda e `a direita os s´ımbolos que ele produz. A deriva¸c˜ao que substitui os s´ımbolos da direita para a esquerda ´e denominada de deriva¸c˜ao mais `a direita e a que substitui da esquerda para a direita ´e deriva¸c˜ao mais `a esquerda.

(20)

8 Uma gram´atica ´e dita amb´ıgua caso uma cadeia de caracteres gerada atrav´es de suas regras possua mais de uma deriva¸c˜ao mais `a esquerda ou mais `a direita. A ´arvore gramatical (ou ´arvore sint´atica) ´e uma representa¸c˜ao gr´afica para uma deriva¸c˜ao, os seus n´os internos s˜ao os s´ımbolos n˜ao terminais e as suas folhas os terminais. Numa gram´atica que n˜ao ´e amb´ıgua as sequˆencias de caracteres geradas por ela possuem uma ´unica ´arvore gramatical.

Existem v´arios poss´ıveis m´etodos de an´alise sint´atica, mas em geral segue uma abordagem Top-down ou Bottom-up. Um m´etodo Bottom-up tenta construir uma ´arvore gramatical para uma sequˆencia de tokens come¸cando das folhas e finalizando na raiz. Nesse caso, uma sequˆencia de tokens ´e decomposta at´e que o s´ımbolo inicial da gram´atica seja alcan¸cado, ou seja, seria como se todos os passos de uma deriva¸c˜ao fossem desfeitos, esse processo ´e denominado redu¸c˜ao. Para encontrar estas redu¸c˜oes, o compilador disp˜oe do algoritmo empilha-reduz. Nele existem duas opera¸c˜oes, uma respons´avel por empilhar um token e outra por desempilhar s´ımbolos terminais e empilhar n˜ao terminais, que s˜ao chamadas, respectivamente, de Avan¸car e Reduzir. A abordagem Top-down realiza uma deriva¸c˜ao mais `a esquerda de uma sequˆencia de tokens a partir do s´ımbolo inicial da gram´atica. A ´arvore gramatical desta sequˆencia ´e constru´ıda da raiz at´e as folhas.

2.1.2.1 Erros sint´aticos

Os erros nessa fase est˜ao ligados `as constru¸c˜oes no c´odigo que burlam as regras que definem a estrutura da linguagem. Para ilustrar alguns erros, mostramos um exemplo de um programa (Algoritmo 2) e em seguida uma descri¸c˜ao sobre seus erros.

Algoritmo 2 Um exemplo de programa com erros sint´aticos x = 10

if x = 1; then x == 10 end if

Suponha que a linguagem do Algoritmo 2 obrigue o uso do s´ımbolo “;” para marcar o fim de uma instru¸c˜ao. O c´odigo acima possui trˆes erros sint´aticos: na primeira linha, a instru¸c˜ao n˜ao possui o “;” (o correto seria “x = 10;”) , na estrutura condicional “x = 1;” n˜ao representa uma express˜ao l´ogica e no interior do bloco desta estrutura, n˜ao s˜ao admitidas express˜oes do tipo “x == 10” (este erro seria corrigido caso as posi¸c˜oes das

(21)

9 instru¸c˜oes fossem trocadas). O erro detectado no Algoritmo 2 foi encontrado atrav´es do processo de parser, que ser´a ilustrado na se¸c˜ao 2.1.2.2 em outro exemplo.

2.1.2.2 Exemplo de an´alise sint´atica

Para ilustrar como funciona a an´alise sint´atica usando a abordagem Bottom-up, duas tabelas especificando as regras de produ¸c˜ao (Tabela 2.3) e os passos do algoritmo empilha-reduz (Tabela 2.4) e um desenho da ´arvore sint´atica (Figura 2.1) da sequˆencia de tokens que foi reduzida s˜ao apresentados a seguir.

0 Goal → Expr

1 Expr → Expr + Term 2 Expr → Expr - Term 3 Expr → Term

4 Term → Term * Factor 5 Term → Term / Factor 6 Term → Factor

7 Factor → num 8 Factor → id 9 Factor → ( Expr )

Tabela 2.3: Regras de Produ¸c˜ao

A sequˆencia de tokens utilizada na tabela 2.4 ´e x - 2 * y. A primeira coluna da tabela 2.4 representa a pilha onde os s´ımbolos s˜ao inseridos e tamb´em s˜ao removidos quando reduzidos a um s´ımbolo n˜ao terminal (neste caso, o s´ımbolo n˜ao terminal substitui os que foram removidos na pilha), na segunda coluna est˜ao os s´ımbolos da sequˆencia que ainda n˜ao foram analisados, a terceira coluna representa o handle que ´e um par h regra de produ¸c˜ao, posi¸c˜ao da subcadeia i que indica a posi¸c˜ao da subcadeia na sequˆencia de tokens e a regra de produ¸c˜ao que ser´a utilizada na redu¸c˜ao dessa subcadeia e a quarta coluna representa a a¸c˜ao que pode ser de avan¸co ou de redu¸c˜ao.

(22)

10

Pilha Entrada Handle A¸c˜ao

$ id - num * id - avan¸ca

$id - num * id 8,1 reduz a 8

$Factor - num * id 6,1 reduz a 6

$Term - num * id 3,1 reduz a 3

$Expr - num * id - avan¸ca

$Expr - num * id - avan¸ca

$Expr - num * id 7,3 reduz a 7

$Expr - Factor * id 7,3 reduz a 6

$Expr - Term * id - avan¸ca

$Expr - Term * id 8,5 reduz a 8

$Expr - Term * Factor 4,5 reduz a 4

$Expr - Term 2,3 reduz a 2

$Expr 0,1 reduz a 0

$Goal - aceita

Tabela 2.4: Passos do Algoritmo Empilha-Reduz

Na figura 2.1 a ´arvore sint´atica da sequˆencia de tokens desse exemplo ´e ilustrada. Como a abordagem ´e Bottom-up esta ´arvore foi constru´ıda das folhas at´e a sua raiz.

(23)

11

2.1.3

An´

alise semˆ

antica

A an´alise semˆantica ´e respons´avel pela verifica¸c˜ao de caracter´ısticas relacionadas ao significado da instru¸c˜ao. O Analisador Semˆantico faz uso da ´arvore sint´atica e da tabela de s´ımbolos para realizar a an´alise semˆantica.

Algumas regras s˜ao imposs´ıveis de serem representadas nas etapas anteriores de an´alise l´exica e sint´atica. Exemplos destas regras s˜ao: toda vari´avel deve ser declarada antes de ser utilizada, o operador deve estar relacionado a operandos de tipos compat´ıveis com a opera¸c˜ao, entre outras.

2.1.3.1 Tabela de S´ımbolos

A tabela de s´ımbolos ´e uma estrutura de dados respons´avel por armazenar todos os dados de um determinado identificador. Na verifica¸c˜ao semˆantica das declara¸c˜oes de vari´aveis, fun¸c˜oes e tipos, os nomes declarados s˜ao inseridos na tabela de s´ımbolos.

Essa tabela associa atributos aos nomes definidos pelo programador, como por exemplo, tipo, escopo, limites para vetores e n´umeros de parˆametros para fun¸c˜oes. A consulta a esta tabela ´e realizada no momento da verifica¸c˜ao do uso desses nomes.

2.1.3.2 Principais erros semˆanticos

Durante a an´alise semˆantica ´e verificado se o c´odigo est´a respeitando algumas regras semˆanticas. Algumas destas regras s˜ao:

Compatibilidade de tipos : O tipo de dado atribu´ıdo a uma determinada vari´avel deve ser compat´ıvel com o tipo da mesma.

Escopo dos identificadores : Vari´aveis e fun¸c˜oes devem estar declaradas em locais que podem ser acessados onde esses identificadores est˜ao sendo utilizados.

Unicidade de nomes de identificadores : Os identificadores devem ser ´unicos. Na tabela 2.5 temos exemplos para cada um dos trˆes tipos de erros semˆanticos.

(24)

12

Tipo Erro

Compatibilidade de tipos int x; x = “teste”; Escopo dos identificadores int func(int y) {

int x = y; return x; } int main() { int z; z = x; }

Unicidade de identificadores int x = 3; int x = 4; int soma;

int func(int x, int y) { x = x + y;

return x; }

Tabela 2.5: Tipos de erros semˆanticos

Na Tabela 2.5 s˜ao apresentados trechos de c´odigo que desrespeitam as regras se-mˆanticas. O primeiro c´odigo apresenta um erro que desrespeita a regra de compatibilidade de tipos, pois tenta atribuir uma cadeia de caracteres a uma vari´avel do tipo inteiro. O segundo c´odigo burla a regra de escopo dos identificadores, pois tenta usar uma vari´avel definida fora do escopo de uma fun¸c˜ao. E o terceiro c´odigo desrespeita a regra de unici-dade de identificadores, pois no c´odigo s˜ao definidas mais de uma vari´avel com o mesmo identificador.

2.2

Etapa de s´ıntese do c´

odigo

Ap´os a an´alise da corretude do c´odigo-fonte, a etapa de s´ıntese do c´odigo ´e alcan-¸cada. Esta etapa ´e dividida em trˆes partes: gera¸c˜ao do c´odigo intermedi´ario, otimiza¸c˜ao

(25)

13 do c´odigo e gera¸c˜ao do c´odigo final.

2.2.1

Gera¸

ao de c´

odigo intermedi´

ario

A representa¸c˜ao intermedi´aria pode ser atrav´es do uso da nota¸c˜ao p´os-fixa, por uma ´arvore sint´atica ou por um c´odigo de trˆes endere¸cos.

A nota¸c˜ao p´os-fixada ´e adotada na representa¸c˜ao de express˜oes aritm´eticas e l´ ogi-cas. As express˜oes aritm´eticas escritas na forma convencional est˜ao em nota¸c˜ao infixada, pois os operadores ficam entre os operandos. Mas para o compilador gerar corretamente o c´odigo de m´aquina, as express˜oes s˜ao reorganizadas em nota¸c˜ao p´os-fixada, a qual cada operador aparece ap´os seus operandos. A tabela 2.6 mostra a diferen¸ca entre as duas formas de representa¸c˜ao de express˜oes aritm´eticas/l´ogicas.

Nota¸c˜ao Exemplo

Infixada A*B/C

P´os-fixada AB*C/

Tabela 2.6: Nota¸c˜oes P´os-fixa e Infixa

Como os operadores possuem diferentes ordens de prioridade, o uso da nota¸c˜ao infixada n˜ao ´e o ideal para a representa¸c˜ao de express˜oes, pois n˜ao define qual opera¸c˜ao deve ser realizada antes das demais que est˜ao numa mesma express˜ao.

A ´arvore sint´atica ´e uma outra forma de representa¸c˜ao intermedi´aria gerada na etapa da an´alise sint´atica como foi explicado na se¸c˜ao anterior.

O c´odigo de trˆes endere¸cos ´e uma representa¸c˜ao mais pr´oxima da estrutura da linguagem Assembly, o que facilita no momento da convers˜ao para esta linguagem. Numa instru¸c˜ao podem ser encontradas no m´aximo trˆes vari´aveis: duas para operadores bin´arios e uma para o resultado. Com isso, qualquer tipo de opera¸c˜ao bin´aria pode ser represen-tada. Os seus tipos b´asicos de instru¸c˜oes s˜ao: express˜oes de atribui¸c˜ao, desvios, invoca¸c˜ao de rotinas e acesso indexado.

Existem trˆes tipos de instru¸c˜oes de atribui¸c˜ao: opera¸c˜ao bin´aria, opera¸c˜ao un´aria e c´opia. Na tabela 2.7 exemplos das trˆes opera¸c˜oes s˜ao ilustrados.

(26)

14

Tipo Instru¸c˜ao

Opera¸c˜ao Bin´aria x := y op z Opera¸c˜ao Un´aria x := op y

C´opia x := y

Tabela 2.7: Instru¸c˜oes de Atribui¸c˜ao

Existem dois tipos de instru¸c˜oes de desvio: desvio incondicional e o desvio condi-cional. Na tabela 2.8 temos exemplos dos dois tipos de desvios.

Tipo Instru¸c˜ao

Desvio condicional if x op y goto L Desvio Incondicional goto L

Tabela 2.8: Instru¸c˜oes de Desvio

A invoca¸c˜ao de rotinas ocorre em duas etapas. Inicialmente, os argumentos do procedimento s˜ao registrados na instru¸c˜ao param, ap´os isso, a instru¸c˜ao call completa o processo de invoca¸c˜ao da rotina. Na tabela 2.9 as instru¸c˜oes usadas na invoca¸c˜ao de uma rotina s˜ao ilustradas.

Tipo Instru¸c˜ao

Registro de argumento param x

Invoca¸c˜ao da rotina t1 := call nome, NUMPARAMS Tabela 2.9: Instru¸c˜oes de Invoca¸c˜ao de Rotina

Existem duas formas de representar o acesso indexado que est˜ao representadas na tabela 2.10.

Tipo Instru¸c˜ao

Acesso Indexado x := y[i] Acesso Indexado y[i] := x

(27)

15

2.2.2

Otimiza¸

ao do c´

odigo

Nessa etapa s˜ao detectadas instru¸c˜oes ineficientes no c´odigo e s˜ao aplicadas es-trat´egias espec´ıficas para cada situa¸c˜ao encontrada nelas, assim, reduzindo a ineficiˆencia do c´odigo. A seguir as estrat´egias: Simplifica¸c˜ao alg´ebrica, Elimina¸c˜ao de subexpress˜ao comum, Propaga¸c˜ao de c´opia e Elimina¸c˜ao de c´odigo morto s˜ao definidas e exemplificadas. Na Simplifica¸c˜ao alg´ebrica, as instru¸c˜oes podem ser removidas ou simplificadas. Aquelas instru¸c˜oes cuja execu¸c˜ao n˜ao afeta em nada o programa s˜ao removidas, al´em destas, existem instru¸c˜oes cuja execu¸c˜ao se torna mais eficiente caso o tipo de opera¸c˜ao que ela deseja executar seja representada de uma forma que torne a sua execu¸c˜ao menos custosa para o computador. A tabela 2.11 mostra um exemplo.

A¸c˜ao Instru¸c˜oes Efeito

Elimina¸c˜ao x := x + 0 x := x * 1 {vazio} {vazio} Simplifica¸c˜ao x := x * 0 y := y ** 2 x := x * 8 x := 0 y := y * y x := x << 3 Tabela 2.11: Simplifica¸c˜ao alg´ebrica

Na Elimina¸c˜ao de subexpress˜ao comum, algumas instru¸c˜oes s˜ao simplificadas al-terando o lado direito delas que possuem opera¸c˜oes que foram executadas anteriormente por outra. Esta altera¸c˜ao ´e a troca da express˜ao, que representa a opera¸c˜ao, pela vari´avel que anteriormente recebeu o resultado da mesma opera¸c˜ao, transformando esta instru¸c˜ao em uma atribui¸c˜ao simples. Mas, para que isso funcione corretamente, a vari´avel que foi posta no lugar da express˜ao n˜ao pode ter alterado seu conte´udo. A tabela 2.12 mostra um exemplo.

Instru¸c˜oes Efeito x := y + z .. . w := y + z x := y + z .. . w := x

(28)

16 Na Propaga¸c˜ao de c´opia, caso exista uma instru¸c˜ao do formato x := y, usos de x ap´os esta instru¸c˜ao podem ser substitu´ıdos por y. A tabela 2.13 mostra um exemplo.

Instru¸c˜oes Efeito b := z + y a := b x := 2 * a b := z + y a := b x := 2 * b Tabela 2.13: Propaga¸c˜ao de c´opia

Na Elimina¸c˜ao de c´odigo morto, uma instru¸c˜ao que n˜ao aparece em qualquer ou-tro lugar no programa e n˜ao afeta no funcionamento do mesmo, est´a morta e pode ser removida. A tabela 2.14 mostra um exemplo.

Instru¸c˜oes Efeito b := z + y a := b x := 2 * a b := z + y {vazio} x := 2 * b

Tabela 2.14: Elimina¸c˜ao de c´odigo morto

Embora a ideia de possuir um c´odigo otimizado seja interessante, na pr´atica, nem sempre ´e mais conveniente implementar o melhor otimizador poss´ıvel. Os motivos s˜ao: algumas modifica¸c˜oes s˜ao dif´ıceis de implementar, algumas s˜ao custosas ao tempo de com-pila¸c˜ao e algumas tem benef´ıcio pequeno e muitas sofrem dos trˆes problemas anteriores. O objetivo, ent˜ao, seria encontrar um otimizador que traga benef´ıcios com um custo baixo.

2.2.3

Gera¸

ao do c´

odigo final

Ap´os a gera¸c˜ao do c´odigo intermedi´ario e a sua otimiza¸c˜ao, chegamos a ´ultima etapa que ´e a gera¸c˜ao do c´odigo final. Nesse projeto o c´odigo final ´e em Assembly MIPS, cada um dos seus 32 registradores s˜ao mostrados na tabela 2.15.

(29)

17

Nome Descri¸c˜ao

$zero Retorna o valor 0.

$at (Assembler Temporary) Reservado pelo assembler.

$v0-$v1 Valores das express˜oes de avalia¸c˜ao e resultados de fun¸c˜ao. $a0-$a3 Primeiros quatro parˆametros para subrotinas.

$t0-$t9 (Tempor´arios) Tempor´arios para quem chama as subrotinas. $s0-$s7 (Tempor´arios) Tempor´arios para subrotinas.

$k0-$k1 Reservados para uso do tratamento de interrup¸c˜ao.

$gp (Global Pointer)

$sp (Stack Pointer) Aponta para o topo da pilha

$s8-$fp (Saved Values/ Frame Pointer) Preservado na chamada de proce-dures.

$ra (Return Adress)

$f0 Recebe o retorno de floats em fun¸c˜oes. $f12/$f14 Usados para passar floats para fun¸c˜oes. ($f12,$f13)

($f14,$f15)

Usados em conjunto para passar doubles para fun¸c˜oes.

Tabela 2.15: Registradores MIPS

As instru¸c˜oes s˜ao de 32 bits e as palavras tem 4 bytes. Um inteiro ocupa uma palavra na mem´oria. Al´em disso, podem ser manipulados 32 registradores.

A tabela 2.16 mostra algumas instru¸c˜oes em Assembly MIPS e seus respectivos significados.

(30)

18

Tipo Instru¸c˜oes Significado

Aritm´etica add reg1, reg1, reg2 reg1 := reg1 + reg2 Aritm´etica sub reg1, reg1, reg2 reg1 := reg1 - reg2 L´ogica and reg1, reg1, reg2 reg1 := reg1 && reg2

L´ogica or reg1, reg1, reg2 reg1 := reg1 || reg2

Movimenta¸c˜ao move reg1, reg2 reg1 := reg2

Desvio Incond. j label goto label

Desvio cond. beq reg1, reg2, label if reg1 = reg2 goto label Tabela 2.16: Algumas instru¸c˜oes em Assembly MIPS

O Algoritmo 3 ´e a implementa¸c˜ao do Algoritmo 1 em Assembly MIPS.

Algoritmo 3 Algoritmo 1 em Assembly MIPS .data x: .word 10 .text L0: lw $t0, x L1: beq $t0, $zero, L2 subi $t0, $t0, 1 j L1 L2: sw $t0, x

(31)

Cap´ıtulo 3

A Linguagem Janus

Uma linguagem de programa¸c˜ao revers´ıvel produz c´odigo que pode ser interrompido em qualquer ponto, revertido para qualquer ponto e executado novamente. As linguagens tradicionais possuem uma mistura de constru¸c˜oes revers´ıveis e irrevers´ıveis, ou seja, seus programas podem ou n˜ao ser revers´ıveis dependendo da constru¸c˜ao utilizada em sua im-plementa¸c˜ao. Um programa irrevers´ıvel pode ser executado reversivelmente com o uso de plataformas revers´ıveis, que simulariam tal execu¸c˜ao, mas isso aumentaria o tempo de execu¸c˜ao e sobrecarregaria a mem´oria. Linguagens revers´ıveis possuem apenas constru-¸c˜oes revers´ıveis o que eliminaria esta sobrecarga na mem´oria e o aumento no tempo de execu¸c˜ao devido a reversibilidade.

A linguagem Janus [5] ´e imperativa, estruturada e revers´ıvel. Por ser revers´ıvel, todas as altera¸c˜oes realizadas sobre o estado de um programa podem ser desfeitas, as-sim voltando a um estado anterior. Janus ´e uma linguagem simples, por´em poderosa o suficiente para desenvolver algoritmos complexos e suas constru¸c˜oes podem servir como modelo para o desenvolvimento de outras linguagens revers´ıveis.

Todas as suas vari´aveis s˜ao globais e do tipo inteiro. Nenhum outro tipo de dados (como ponto flutuante ou string) ´e suportado. Embora isso simplifique a linguagem em rela¸c˜ao a completude e reversibilidade, s˜ao necess´arios mais tipos para fins mais pr´aticos em muitas aplica¸c˜oes.

(32)

20

3.1

Gram´

atica

Uma gram´atica formal ´e um conjunto de regras de produ¸c˜ao de cadeias em uma linguagem formal. A partir dessas regras, podemos verificar se uma sequˆencia de caracteres oriunda de um c´odigo-fonte segue as regras da gram´atica da linguagem alvo. Gram´aticas de linguagens formais podem ser representadas atrav´es de um c´odigo chamado EBNF que ´e composto por s´ımbolos terminais, n˜ao-terminais e regras de produ¸c˜ao.

A seguir temos a gram´atica da linguagem Janus escrita na nota¸c˜ao EBNF.

Program ::= { ident [ ‘[’ num ‘]’ ] }*

{ ‘PROCEDURE’ ident Statements }* Statements ::= Ifstmt Statements

| Dostmt Statements | Callstmt Statements | Readstmt Statements | Writestmt Statements | Lvalstmt Statements | null

Ifstmt ::= ‘IF’ Expression

| [ ‘THEN’ Statements ] | [ ‘ELSE’ Statements ] | ‘FI’ Expression

Dostmt ::= ‘FROM’ Expression | [‘DO’ Statements] | [‘LOOP’ Statements] | ‘UNTIL’ Expression Callstmt ::= ‘CALL’ ident

| ‘UNCALL’ ident Readstmt ::= ‘READ’ ident Writestmt ::= ‘WRITE’ ident Lvalstmt ::= Lvalue Modstmt

(33)

21 Modstmt ::= ‘+=’ Expression | ‘-=’ Expression | ‘!=’ Expression Swapstmt ::= ‘:’ Lvalue Expression ::= Minexp

| Minexp binop Expression Minexp ::= ‘(’ Expression ‘)’ | ‘-’ Expression | ‘~’ Expression | Lvalue | Constant Lvalue ::= ident | ident ‘[’ Expression ‘]’ Constant ::= num

{ }* Indica zero ou mais repeti¸c˜oes , [ ] indica zero ou uma repeti¸c˜ao e os s´ımbolos terminais est˜ao entre aspas simples.

Os identificadores, representados por ident na gram´atica, s˜ao formados por qual-quer sequˆencia de letras que n˜ao constituem uma palavra reservada. Os n´umeros, repre-sentados por num na gram´atica, s˜ao formados por qualquer sequˆencia de d´ıgitos decimais. Na linguagem Janus s˜ao encontrados os operadores bin´arios e un´arios que esta-mos acostumados a ver nas linguagens tradicionais. A tabela 3.1 esta-mostra os operadores aritm´eticos bin´arios da linguagem.

Nome S´ımbolo Soma + Subtra¸c˜ao -Multiplica¸c˜ao * Divis˜ao / Resto \

(34)

22 A tabela 3.2 mostra os operadores l´ogicos bin´arios da linguagem.

Nome S´ımbolo

XOR !

OR |

AND &

Tabela 3.2: Operadores l´ogicos bin´arios

A tabela 3.3 mostra os operadores relacionais bin´arios da linguagem.

Nome S´ımbolo Menor que < Maior que > Igual = Diferente # Menor ou igual a <= Maior ou igual a >=

Tabela 3.3: Operadores relacionais bin´arios

Al´em dos operadores bin´arios, tamb´em existem os un´arios que est˜ao representados na tabela 3.4.

Nome S´ımbolo

NOT ~

Negativo

-Tabela 3.4: Operadores un´arios

Al´em dos operadores bin´arios e un´arios, existem os operadores de modifica¸c˜ao e o de Swap que s˜ao os ´unicos capazes de trocar valores de vari´aveis. Os operadores de modifica¸c˜ao avaliam a express˜ao `a direita e modificam a vari´avel `a esquerda de acordo com o operador. O operador += adiciona a express˜ao na vari´avel, -= subtrai e != aplica a opera¸c˜ao l´ogica XOR. O operador Swap troca os valores das vari´aveis `a esquerda e `a direita. Estes operadores est˜ao representados na tabela 3.5.

(35)

23 Nome S´ımbolo Operadores de Modifica¸c˜ao += -= != Operador Swap :

Tabela 3.5: Operadores de Modifica¸c˜ao e Swap

3.2

Reverso de Instru¸

oes

Para que cada parte do programa possa ser executada de forma reversa, ´e necess´ario que cada instru¸c˜ao possua sua equivalente na forma reversa. A tabela 3.6 mostra a instru¸c˜ao e seu reverso.

Instru¸c˜ao Reverso

var != expression var != expression

a : b a : b

READ name WRITE name

WRITE name READ name

S1 S1−1 IF e1 THEN S1 ELSE S2 FI e2 IF e2 THEN S1−1 ELSE S2−1 FI e1 FROM e1 DO S1 LOOP S2 UNTIL e2 FROM e2 DO S1−1 LOOP S2−1 UNTIL e1

CALL procedureName UNCALL procedureName

var += expression var -= expression

var -= expression var += expression

(36)

24 Um procedimento pode ser executado no sentido progressivo atrav´es da instru-¸c˜ao CALL e no sentido reverso pela instru¸c˜ao UNCALL. A dire¸c˜ao de execu¸c˜ao de um procedimento pode ser alternada cada vez que UNCALL ´e usado.

A instru¸c˜ao READ troca o valor de uma vari´avel por um valor inserido pelo usu´ario e a instru¸c˜ao WRITE mostra o valor de uma vari´avel. Um procedimento que possua uma instru¸c˜ao READ, quando executado no sentido reverso esta instru¸c˜ao se torna uma instru¸c˜ao WRITE e vice-versa.

3.3

Estruturas de Controle

A Linguagem Janus, assim como a maior parte das linguagens imperativas, possui estruturas de desvio condicional e de repeti¸c˜ao.

3.3.1

Estrutura Condicional

FORWARD REVERSE IF e1 THEN S1 ELSE S2 FI e2 IF e2 THEN S1−1 ELSE S2−1 FI e1

Tabela 3.7: Estrutura Condicional

A estrutura condicional da linguagem Janus mostrada na tabela 3.7 ´e semelhante `

as estruturas de linguagens convencionais, a ´unica diferen¸ca ´e o acr´escimo da express˜ao l´ogica no fim. A express˜ao l´ogica que est´a ap´os FI e a que est´a ap´os o IF devem ter o mesmo valor-verdade. Caso contr´ario, um erro ocorrer´a e o programa ser´a abortado. Quando a express˜ao l´ogica e1 for verdadeira, todos os comandos que fazem parte de S1, que est˜ao ap´os o THEN, s˜ao executados, ao terminar de executar a ´ultima instru¸c˜ao nesse trecho, ´e verificado o valor-verdade da express˜ao l´ogica e2 que est´a ap´os o FI e que deve ser igual ao valor-verdade de e1. As estruturas definidas como FORWARD e REVERSE, as quais foram representadas no c´odigo acima, definem o padr˜ao seguido para reverter `as modifica¸c˜oes realizadas no estado do programa. Todos os comandos que fazem parte de

(37)

25 S1 e S2 s˜ao revertidos e sua nova vers˜ao ´e representada por S1−1 e S

−1

2 . Al´em disso, as express˜oes l´ogicas que est˜ao ap´os IF e FI s˜ao trocadas.

3.3.2

Estrutura Iterativa

FORWARD REVERSE FROM e1 DO S1 LOOP S2 UNTIL e2 FROM e2 DO S1−1 LOOP S2−1 UNTIL e1 Tabela 3.8: Estrutura Iterativa

A estrutura iterativa mostrada na tabela 3.8 tem um funcionamento diferente das demais estruturas encontradas em linguagens convencionais. Seu funcionamento ´e descrito da seguinte maneira:

1. Avalia a express˜ao l´ogica e1 de FROM. Se for verdadeira, avan¸ca para o segundo passo. Sen˜ao, aborta o programa.

2. Executa os comandos de S1 de DO e avan¸ca para o terceiro passo.

3. Avalia a express˜ao l´ogica e2 de UNTIL. Se for verdadeira, sai da estrutura iterativa. Sen˜ao, avan¸ca para o quarto passo.

4. Executa os comandos de S2 de LOOP e avan¸ca para o quinto passo.

5. Avalia novamente a express˜ao l´ogica e1 de FROM e verifica se o seu valor-verdade corresponde ao valor-verdade da express˜ao l´ogica e2 de UNTIL. Se for igual, retorna ao segundo passo. Sen˜ao, aborta o programa.

As modifica¸c˜oes realizadas na estrutura REVERSE s˜ao semelhantes `as que ocor-reram no caso da estrutura condicional. As express˜oes l´ogicas que est˜ao ap´os o FROM e o UNTIL s˜ao trocadas, isto ´e, a express˜ao e1 ficar´a ap´os o UNTIL e a express˜ao e2 ap´os o FROM. As instru¸c˜oes que fazem parte de S1 e S2 tamb´em s˜ao alteradas, suas novas vers˜oes representadas por S1−1 e S2−1 est˜ao no c´odigo REVERSE.

(38)

Cap´ıtulo 4

Compilador para Linguagem Janus

Conforme foi comentado anteriormente, o compilador foi feito em Java, na vers˜ao 1.8.0 131. Para construir o Analisador L´exico, foi usado o JFlex e para o Analisador Sint´atico foi utilizado o Java Cup, conforme ser´a explicado nas pr´oximas se¸c˜oes. Nas se¸c˜oes seguintes, s˜ao descritas as etapas de An´alise Semˆantica e Gera¸c˜ao de C´odigo.

A implementa¸c˜ao deste compilador pode ser encontrada no link: https://github.com/vbrum/JanusCompiler .

4.1

Gerador do Analisador L´

exico

O Analisador L´exico foi constru´ıdo com o aux´ılio do JFlex [2], vers˜ao 1.6.1, que ´e uma biblioteca geradora de Analisador L´exico. Para constru´ı-lo recebe como entrada uma especifica¸c˜ao de um conjunto de express˜oes regulares e a¸c˜oes, que gera um programa, conhecido como Scanner, que lˆe a entrada, combina as entradas com as express˜oes regu-lares especificadas no arquivo que possui extens˜ao lex, e executa as a¸c˜oes correspondentes `

a express˜ao regular combinada.

4.2

Gerador do Analisador Sint´

atico

O Analisador Sint´atico foi gerado pela biblioteca Java Cup [1], vers˜ao 11a. A forma de lidar com o Java Cup ´e an´aloga a do JFlex. Um LALR Parser ´e gerado atrav´es dessa biblioteca a partir de informa¸c˜oes encontradas num arquivo de extens˜ao cup. Nesse arquivo podem ser inseridas v´arias informa¸c˜oes relevantes para a cria¸c˜ao do Parser, como

(39)

27 importa¸c˜oes de classes, trechos de c´odigos implementados pelo usu´ario para incluir alguma funcionalidade, defini¸c˜ao dos s´ımbolos terminais e n˜ao terminais, e, por fim, as regras de produ¸c˜ao da linguagem e suas a¸c˜oes semˆanticas.

Ap´os a execu¸c˜ao do arquivo cup, dois arquivos s˜ao gerados. A classe respons´avel pelo Parser e outra contendo os s´ımbolos terminais e contantes relacionadas a eles.

4.3

Analisador Semˆ

antico

Ap´os a constru¸c˜ao bem sucedida da ´arvore sint´atica, o processo de compila¸c˜ao de um programa na linguagem Janus chega a uma nova etapa, a qual ´e respons´avel pela an´alise semˆantica do programa. A estrutura da ´arvore sint´atica ´e acessada diversas vezes nessa parte, cada acesso realizado tem como objetivo uma diferente funcionalidade da an´alise semˆantica.

4.3.1

Padr˜

ao de projeto Visitor

Na an´alise semˆantica a ´arvore sint´atica ´e acessada diversas vezes, mas cada vez com objetivos diferentes. Para tornar simples a implementa¸c˜ao desse acesso, o padr˜ao de projeto Visitor foi escolhido. Uma das vantagens deste padr˜ao ´e a habilidade de adicionar novas opera¸c˜oes a uma estrutura j´a existente.

O padr˜ao de projeto Visitor faz uso de uma interface que possui todos os m´etodos de visita¸c˜ao dos n´os da ´arvore sint´atica. Com isso, as diferentes funcionalidades da an´alise semˆantica est˜ao implementadas em classes que implementam esta interface. Cada n´o da ´

arvore possui um m´etodo que aceita um determinado comportamento implementado na classe passada como seu argumento.

4.3.2

Tabela de s´ımbolos

A tabela de s´ımbolos foi constru´ıda com duas Hashtables, uma para Procedures-SymbolTable, classe que especifica os atributos de uma tabela de s´ımbolos para procedure, e outra para as vari´aveis. Tendo em vista que n˜ao existe vari´avel local e os procedures n˜ao retornam nenhum tipo de dado, ent˜ao a tabela de s´ımbolos de procedure s´o possui os seus identificadores. As vari´aveis possuem alguns atributos como nome, registrador e tipo da vari´avel (tempor´aria ou constante).

(40)

28 A tabela de s´ımbolos ´e constru´ıda a medida que os n´os da ´arvore sint´atica s˜ao visitados. Essa constru¸c˜ao ´e realizada atrav´es da classe BuildSymbolTableVisitor que implementa os m´etodos respons´aveis por capturar informa¸c˜oes dos n´os da ´arvore e guarda-las na tabela de s´ımbolos.

4.3.3

Verifica¸

ao de erros

Existem trˆes classes respons´aveis pela verifica¸c˜ao da poss´ıvel ocorrˆencia de erros semˆanticos: BuildSymbolTableVisitor, UndefinedVariableVisitor e TypeCheckingVisitor. A tabela 4.1 mostra os erros detect´aveis por cada uma dessas classes.

Classe Papel

BuildSymbolTableVisitor Construir a tabela de s´ım-bolos.

Verificar se as vari´aveis fo-ram definidas mais de uma vez.

UndefinedVariableVisitor Verificar se as vari´aveis que est˜ao sendo utilizadas foram definidas.

TypeCheckingVisitor Verificar se os valores atri-bu´ıdos `as vari´aveis s˜ao com-pat´ıveis com os seus tipos. Verificar se as express˜oes nas condi¸c˜oes do IfState-ment/DoStatement s˜ao bo-oleanas.

Tabela 4.1: Erros semˆanticos

Todas as classes respons´aveis pela detec¸c˜ao de erros semˆanticos percorrem os n´os da ´

arvore sint´atica, mas cada uma com uma estrat´egia especifica de detec¸c˜ao. Encontrando algum erro, o processo de compila¸c˜ao ´e abortado e o erro ´e reportado.

(41)

29

4.4

Gera¸

ao de c´

odigo

Depois de passar pela an´alise semˆantica sem que tenha sido detectado algum erro semˆantico, a parte final do processo de compila¸c˜ao ´e alcan¸cada, a gera¸c˜ao de c´odigo. Esta etapa ´e dividida em trˆes partes: Gera¸c˜ao de c´odigo, otimiza¸c˜ao do c´odigo gerado e gera¸c˜ao do c´odigo Assembly MIPS.

4.4.1

Representa¸

ao Intermedi´

aria

Para representar o c´odigo intermedi´ario, instru¸c˜oes quadruplas foram definidas. Na tabela 4.2 detalhes sobre cada uma dessas instru¸c˜oes s˜ao mostrados.

Opera¸c˜ao IR C´odigo de trˆes endere¸cos

Assignment x := y op z

Unary Assignment x := op y

Copy x := y

Unconditional Jump goto Label

Conditional Jump iffalse x goto Label

Parameter param x

Call x := call f, NUMPARAMS

Indexed Assignment x := y[i] y[i] := x

Tabela 4.2: Instru¸c˜oes quadruplas

No processo de gera¸c˜ao do c´odigo intermedi´ario, labels e instru¸c˜oes quadruplas s˜ao criadas ao percorrer a ´arvore sint´atica. As labels s˜ao relacionadas `as instru¸c˜oes, em alguns momentos, com aquelas que iniciam um bloco e em outros com as que finalizam um bloco. As que iniciam um bloco podem estar relacionadas a um novo procedure, ao bloco do else numa estrutura condicional ou aos blocos do do e do loop numa estrutura de repeti¸c˜ao e que s˜ao alcan¸c´aveis atrav´es do uso de instru¸c˜oes do tipo Unconditional Jump ou Conditional Jump, e as que finalizam, s˜ao as que se localizam no fim dos blocos citados anteriomente e s˜ao importantes para o caso de n˜ao ter que executar as instru¸c˜oes que est˜ao num bloco espec´ıfico. As instru¸c˜oes quadruplas criadas s˜ao armazenadas numa lista de instru¸c˜oes que ser´a utilizada no processo de convers˜ao do c´odigo intermedi´ario

(42)

30 para o c´odigo em Assembly, por isso, a ordem das instru¸c˜oes nesta lista ´e de extrema importˆancia.

Como citado antes, o c´odigo numa linguagem reversiva tem duas vers˜oes, a forward e a reverse. Ent˜ao, ap´os gerar o c´odigo intermedi´ario da vers˜ao forward, ´e necess´ario fazer o mesmo para a outra vers˜ao. Os passos para tal opera¸c˜ao s˜ao os mesmos, a ´unica diferen¸ca ´e a ordem de apari¸c˜ao das instru¸c˜oes.

4.4.2

Assembly MIPS

Ap´os a cria¸c˜ao das instru¸c˜oes intermedi´arias, o compilador chega a sua etapa final, o momento de convers˜ao do c´odigo intermedi´ario para o c´odigo em Assembly MIPS. Nesta etapa, as subrotinas, cada uma vinculada a um ´unico tipo de instru¸c˜ao intermedi´aria, s˜ao as protagonistas, pois possuem o conjunto de instru¸c˜oes em Assembly equivalente a instru¸c˜ao a qual est˜ao relacionadas.

O processo de convers˜ao ´e realizado selecionando uma instru¸c˜ao intermedi´aria por vez, verificando se existe alguma label relacionada a esta instru¸c˜ao, caso exista, ´e inclu´ıda no arquivo do c´odigo antes ou depois dela. Ent˜ao, a rotina vinculada a instru¸c˜ao selecio-nada ´e chamada para incluir o conjunto do c´odigo equivalente no arquivo. Este processo ´e repetido at´e que todas as instru¸c˜oes sejam convertidas.

Como Assembly Mips possui poucos registradores tempor´arios, ent˜ao a manipula-¸c˜ao destes deve ser realizada com cuidado, pois existem programas que utilizam muitas vari´aveis tempor´arias e, caso n˜ao existisse um controle dos registradores, n˜ao teria como realizar mais nenhuma opera¸c˜ao por falta deles. Para que n˜ao ocorra esse tipo de pro-blema, todos os valores, inclusive das vari´aveis tempor´arias, s˜ao guardados em mem´oria, assim deixando os registradores livres.

Por ser uma linguagem revers´ıvel, s˜ao gerados dois c´odigos para um mesmo pro-grama, um para a vers˜ao forward e outro para vers˜ao reverse. Todos os c´odigos gerados s˜ao concatenados no mesmo arquivo de extens˜ao asm, juntamente com as fun¸c˜oes pr´ e-definidas como write, read, exit e as exce¸c˜oes.

O programa na linguagem Assembly MIPS ´e executado atrav´es do MARS Simulator [3].

(43)

Cap´ıtulo 5

Conclus˜

ao

Neste projeto, foi desenvolvido um compilador para a linguagem revers´ıvel Janus. Embora a linguagem seja revers´ıvel, caracter´ıstica n˜ao encontrada nas linguagens mais populares, foi seguido o modelo adotado em compiladores convencionais.

A constru¸c˜ao dos Analisadores L´exico e Sint´atico foi facilitada atrav´es do uso das bibliotecas que tem o papel de ger´a-los. As fases de an´alise semˆantica e gera¸c˜ao de c´odigo tiveram as suas estruturas implementadas para que fossem geradas a tabela de s´ımbolos e o c´odigo final, esta ´ultima etapa foi a mais complicada, pois envolve a linguagem Assembly e detalhes que n˜ao s˜ao relevantes quando a linguagem ´e de mais alto n´ıvel, como a manipula¸c˜ao de registradores. Al´em da dificuldade da cria¸c˜ao deste compilador, a linguagem Janus traz consigo detalhes que dificultam seu entendimento, pois todas as suas instru¸c˜oes e estruturas est˜ao adaptadas para revers˜ao, embora a l´ogica por tr´as destas estruturas seja semelhante a de linguagens convencionais, existem detalhes inclu´ıdos justamente para garantir a reversibilidade que podem dificultar a assimila¸c˜ao.

Alguns motivos tornaram o desenvolvimento deste projeto um grande desafio, o conceito de reversibilidade, o tamanho do projeto e a variedade de conceitos da ´area da computa¸c˜ao usados na sua cria¸c˜ao. Diante de todas estas dificuldades, o objetivo foi alcan¸cado, o desenvolvimento do compilador para Janus em Java.

(44)

Referˆ

encias Bibliogr´

aficas

[1] CUP. http://www2.cs.tum.edu/projects/cup/. [2] JFlex. http://jflex.de/.

[3] Mars Simulator. http://courses.missouristate.edu/KenVollmar/mars/.

[4] Alfred V. Aho, Ravi Sethi e Jeffrey D. Ullman. Compiladores: Princ´ıpios, T´ecnicas e Ferramentas. LTC, 1995.

[5] Christopher Lutz e Howard Derby. Janus: a time-reversible language. Caltech class project, 1982.

[6] Kalyan S Perumalla. Introduction to reversible computing. CRC Press, 2013.

[7] Tetsuo Yokoyama e Robert Gl¨uck. A reversible programming language and its inverti-ble self-interpreter. Em Proceedings of the 2007 ACM SIGPLAN symposium on Partial evaluation and semantics-based program manipulation, pp. 144–153. ACM, 2007.

Referências

Documentos relacionados

O Diretor de Relações com Investidores encaminhou declaração a este Agente Fiduciário datada de 25/03/2008 atestando que durante o exercício de 2008, a Companhia cumpriu

O Terceiro Setor nasce para suprir as necessidades da sociedade, nas áreas da assistência social, saúde, cultura, educação, entre outras, onde os órgãos públicos não

1 - Nos casos de prestação de traba- lho extraordinário em dia de descanso semanal obrigatório motivado pela falta imprevista do trabalhador que deveria ocupar o posto

Pode rmacos segue o ritmo

Conclusões a A altura, o diâmetro e as produções de massa seca de folha, caule e raiz das plantas de Atriplex nummularia são variáveis sensíveis à umidade do solo, sendo as

Algumas das formas narrativas mais simples das fotografias em quadrinhos silenciosos sublinham esta função expositiva: fotografias formais, emolduradas de uma família

Em todos os grupos, pré-intervenção, avaliou- se: estágio da doença pela Escala de Hoehn-Yahr modificada; rastreio cognitivo pelo Mini-Exame do Estado Mental; nível de

A função controle se faz necessária em todos os patamares e setores, responsabilizando as pessoas de acordo com a escala hierárquica da organização (KOONTZ, et