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 ab a|b [abc] reconhece a reconhece a seguido de b reconhece a ou b recohece a, b ou c

[^abc] reconhece qualquer caractere, exceto a, b e c [a-z] a* a+ a? reconhece a, b, c, ... ou z reconhece zero ou mais a's reconhece um ou mais a's reconhece um a ou nenhum a.

(a|b)* reconhece qualquer nmero de a's ou b's . \123 reconhece qualquer caractere, exceto quebra de linha 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 \r \s \t \b \e Line Feed Carriage Return Espao Tabulao Backspace 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 C++
throw new SemanticError(msg, pos) 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