Você está na página 1de 8

Tutorial Criando um Pequeno Compilador

Neste tutorial ser criado, com instrues passo a passo, um pequeno compilador, ou
para ser mais preciso um interpretador, para expresses numricas.
Neste interpretador sero aceitas espresses com nmeros, as quatro operaes bsicas e
parnteses. Multiplicao e diviso devero ter prioridade maior que adio e subtrao.
Sero detalhadas as especificaes lxica e sinttica, ser implementado o analisador
semntico e por fim ser feito um programa para testar os analisadores.
Para este tutorial sero feitos o analisador lxico e o sinttico, e os exemplos de cdigo
sero em Java, mas em alguns casos sero tambm dados exemplos em C++ e em
Delphi para mostrar as difernas.

Especificao Lxica
A primeira coisa a se fazer em uma especificao lxica e saber quais os tokens que
devero ser reconhecidos pelo analisador.
Como neste exemplo vo ser precisos nmeros, operadores e parnteses, j possvel
ter uma idia de quais tokens sero precisos:

NUMERO
+
*
/
(
)

Antes de especificar de fato so tokens, preciso notar que a especificao lxica


dividida em duas partes: Definies Regulares e Definio dos Tokens.
Os tokens so definidos na segunda parte. Nas definies regulares so definidas
expresses auxilires, para serem utilizadas na definio dos tokens.

Definies Regulares
Pare este exemplo ser feira apenas a seguinte definio:
D : [0-9]

Esta definio diz que D (digito) qualquer letra entre 0 e 9.

Tokens

Os tokens para este exemplo so definidos da seguinte forma:


"+"
"-"
"*"
"/"
"("
")"
NUMERO : {D}+
: [\s\t\n\r]*

Primeiro so definidos os operadores. Uma grupo de caracteres entre aspas define um


tokens cuja representao a de string entre aspas.
Em seguida definido NUMERO. Para este token fornecida uma expresso regular
para represent-lo. Nesta expresso utilizada a definio regular anteriormente
definida. Um NUMERO um D (digito) repetido uma ou mais vezes. Para utilizar uma
definio deve-se coloc-la entre { e }.
Por fim descrita uma expresso sem um token associado. Isto indica ao analisador que
ele deve ignorar esta expresso sempre que encontr-la. Neste caso devem ser ignorados
espao em branco (\s), tabulao (\t) e quebra de linha (\n e \r).

Expresses Regulares
Esta tabela ilustra as possbilidades de expresses regulares. Quaisquer combinaes
entre estes padres possvel. Espaos em branco so ignorados (exceto entre " e ").
a

reconhece a

ab

reconhece a seguido de b

a|b

reconhece a ou b

[abc]

recohece a, b ou c

[^abc] reconhece qualquer caractere, exceto a, b e c


[a-z]

reconhece a, b, c, ... ou z

a*

reconhece zero ou mais a's

a+

reconhece um ou mais a's

a?

reconhece um a ou nenhum a.

(a|b)* reconhece qualquer nmero de a's ou b's


.

reconhece qualquer caractere, exceto quebra de linha

\123

reconhace o caractere ASCII 123 (decimal)

Os operadores posfixos (*, + e ?) tem prioridade mxima. Em seguida est a


concatenao e por fim a unio ( | ). Parenteses podem ser utulisador para agrupar
smbolos e driblar prioridades.

Caracteres especiais
Os caracteres " \ | * + ? ( ) [ ] { } . ^ - possuem significado especial. Para
utiliz-los como caracteres normais deve-se preced-los por \, ou coloc-los entre " e ".
Qualquer sequancia de caractateres entre " e " tratada como caracteres ordinrios.
\+

reconhece +

"+*"

reconhece + seguido de *

"a""b" reconhece a, seguido de ", seguido de b


\"

reconhece "

Existem ainda os caracteres no imprimveis, representados por sequancias de escape


\n

Line Feed

\r

Carriage Return

\s

Espao

\t

Tabulao

\b

Backspace

\e

Esc

\XXX O caractere ASCII XXX (XXX um nmero decimal)

Outras Formas de especificar tokens


Pode-se definir ainda um tokens como sendo um caso particular de um outro token. Por
exemplo:
ID : [a-z A-Z][a-z A-Z 0-9]* //letra seguida de zero ou mais letras ou
dgito
BEGIN = ID : "begin"
END
= ID : "end"
WHILE = ID : "while"

Assim define-se que BEGIN, END e WHILE so casos especiais de ID. Sempre que o
analisador encontrar um ID ele procura na lista de casos especiais para ver se este ID
no um BEGIN ou um WHILE.

Especificao Sinttica
A especificao sinttica feita de produes. As produes para uma gramtica para
expresses nmericas da forma especificada ficam da seguinte forma:
<E> ::= <E> "+" <T>
| <E> "-" <T>
| <T>;
<T> ::= <T> "*" <F>
| <T> "/" <F>
| <F>;
<F> ::= "(" <E> ")" | NUMERO;
Podem ser utilizados na gramtica qualquer token j declarado como smbolo terminal.
Os smbolos no-terminais precisam ser previamente declarados em sua rea especfica.
Esta gramtica possui recurses esquerda e no est fatorada. No possvel processla com um analisador preditivo sem que antes a gramtica seja transformada. Neste
exemplo ser feito um analisador SLR, portanto a gramtica j est pronta.
Inserindo as aes semnticas na gramtica ela fica assim:
<E> ::= <E> "+" <T> #2
| <E> "-" <T> #3
| <T>;
<T> ::= <T> "*" <F> #4
| <T> "/" <F> #5
| <F>;
<F> ::= "(" <E> ")" | NUMERO #1;
As aes so distribuidas j pensando-se na anlise semntica. A ao 1 acontece aps
um NUMERO ser encontrado. Sua implementao ir calcular o valor numrico do
token NUMERO e empilh-lo.
As demais aes iro desempilhar dois valores, efetuar uma operao sobre eles e
empilhar o resultado.

Implementao do Semntico
gerada uma classe para o analisador semntico. Sua implementao porm por conta
do usurio.
O nico mtodo que o analisador semntico possu o mtodo executeAction(). O
analisador sinttico chama ele sempre que uma ao semntica encontrada. So
passados de pametro para este mtodo o nmero da ao semntica que o disparou, e o
ltimo token reconhecido antes da ao.
Para este exemplo, o analisador semntico vai precisar apenas de uma pilha para avaliar
as expresses. Em casos mais complexos, como um compilador, ser preciso uma tabela
de smbolos tambm. E o gerador de cdigo deve ser acionado por aes semnticas
tambm.
import java.util.Stack;
public class Semantico implements Constants
{
Stack stack = new Stack();
public int getResult()
{
return ((Integer)stack.peek()).intValue();
}
public void executeAction(int action, Token token) throws
SemanticError
{
Integer a, b;
switch (action)
{
case 1:
String tmp = currentToken.getLexeme();
if (tmp.charAt(0) == '0')
throw new SemanticError("Nmeros comeados por 0 no so
permitidos", token.getPosition());
stack.push(Integer.valueOf(tmp));
break;
case 2:
b = (Integer) stack.pop();
a = (Integer) stack.pop();
stack.push(new Integer(a.intValue() + b.intValue()));
break;
case 3:
b = (Integer) stack.pop();
a = (Integer) stack.pop();
stack.push(new Integer(a.intValue() - b.intValue()));
break;
case 4:
b = (Integer) stack.pop();
a = (Integer) stack.pop();
stack.push(new Integer(a.intValue() * b.intValue()));
break;
case 5:

}
}

b = (Integer) stack.pop();
a = (Integer) stack.pop();
stack.push(new Integer(a.intValue() / b.intValue()));
break;

Este um exemplo simples, todas as aes so implementadas diretamente dentro do


switch. Um caso mais complexo deve ter um mtodo para cada ao, utilizando o switch
para selecionar o mtodo.
Foi feita uma restrio semntica para exemplificar como devem ser indicados os erros
semnticos. Se o analisador encontra um erro, ele deve lanar um SemanticError,
passando como parmetro a mensagem de erro e a posio do erro (que em geral a
posio do ltimo token).
Java

throw new SemanticError(msg, pos)

C++

throw SemanticError(msg, pos)

Delphi raise ESemanticError.create(msg, pos)

Criando um Programa de Teste:


Este um programa para testar os analisadores. Ele l uma expresso da entrada, e
imprime seu resultado em seguida.
public class Main
{
public static void main(String[] args)
{
Lexico lexico = new Lexico();
Sintatico sintatico = new Sintatico();
Semantico semantico = new Semantico();
LineNumberReader in = new LineNumberReader(new
InputStreamReader(System.in));
String line = in.readLine();
lexico.setInput( line );
try
{
sintatico.parse(lexico, semantico);
System.out.println(" = ");
System.out.println(trans.getResult());
}
catch ( LexicalError e )
{
e.printStackTrace();
}

catch ( SintaticError e )
{
e.printStackTrace();
}
catch ( SemanticError e )
{
e.printStackTrace();
}

}
}

Para utilizar os analisadores gerados pelo GALS, deve seguir os seguintes passos:

Instanciar um objeto de cada analisador


Passar o texto de entrada para o lxico
Chamar o mtodo parse do sinttico, tratando os possveis erros

Em Java
Lexico lexico = new Lexico();
Sintatico sintatico = new Sintatico();
Semantico semantico = new Semantico();
...
lexico.setInput( /* entrada */ );
try
{
sintatico.parse(lexico, semantico);
}
catch ( LexicalError e )
{
//Trada erros lxicos
}
catch ( SintaticError e )
{
//Trada erros sintticos
}
catch ( SemanticError e )
{
//Trada erros semnticos
}

Em C++
Lexico lexico;
Sintatico sintatico;
Semantico semantico;
...
lexico.setInput( /* entrada */ );
try
{
}

sintatico.parse(&lexico, &semantico);

catch ( LexicalError &e )


{
//Trada erros lxicos
}
catch ( SintaticError &e )
{
//Trada erros sintticos
}
catch ( SemanticError &e )
{
//Trada erros semnticos
}

Em Delphi
lexico : TLexico;
sintatico : TSintatico;
semantico : TSemantico;
...
lexico := TLexico.create;
sintatico := TSintatico.create;
semantico := TSemantico.create;
...
lexico.setInput( /* entrada */ );
try
sintatico.parse(lexico, semantico);
except
on e : ELexicalError do
//Trada erros lxicos
on e : ESintaticError do
//Trada erros sintticos
on e : ESemanticError do
//Trada erros semnticos
end;
...
lexico.destroy;
sintatico.destroy;
semantico.destroy;

Mesagens de Erro
So geradas mensagens de erro default para os possvies erros. Em alguns casos elas
podem ser apropriadas, mas em geral voc vai querer alter-las para informar ao usurio
uma mensagem mais adequada. As tabelas de erro esto nos arquivos com as constantes.

Você também pode gostar