Análise sintática Função, interação com o compilador Análise descendente e ascendente Especificação e reconhecimento de cadeias de tokens válidas Implementação Tratamento de erros Prof. Thiago A. S. Pardo taspardo@icmc.usp.br 1 Tratamento de erros Funções Deve relatar a presença de erros de forma clara e precisa Deve se recuperar de cada erro para continuar a análise do programa Pode reparar alguns erros Erro sintático Programa não condiz com a gramática da linguagem: símbolo esperado não encontrado A realização efetiva do tratamento de erros pode ser uma tarefa difícil 2 1
Tratamento de erros Felizmente, a maioria dos erros são simples Pesquisa com estudantes de Pascal 80% dos enunciados contém apenas um erro; 13% tem dois 90% são erros em um único token 60% são erros de pontuação: p.ex., uso do ponto e vírgula (;) 20% são erros de operadores e operandos: p.ex., omissão de : no símbolo := 15% são erros de palavras-chave: p. ex., erros ortográficos (wrteln) 3 Tratamento de erros O tratamento inadequado de erros pode introduzir uma avalanche de erros espúrios, que não foram cometidos pelo programador, mas pelo tratamento de erros realizado Tratamento de erros deve ser cauteloso e selecionar os tipos de erros que podem ser tratados para se obter um processamento eficiente Como analisar um programa Fortran em um compilador Pascal? 4 2
Tratamento de erros Muitas técnicas para o tratamento de erros Nenhuma delas se mostrou universalmente aceitável Poucos métodos têm ampla aplicabilidade Artesanal, muitas vezes Estratégias de tratamento de erro Modo de pânico Recuperação de frases Produções de erro Correção global 5 Tratamento de erros Modo de pânico Método mais simples e fácil de implementar; usado pela maioria dos analisadores Ao encontrar um erro 1. Relata-se o erro 2. Pulam-se tokens até que um token de sincronização seja encontrado Tokens de sincronização: pontos do programa (palavras-chave, delimitadores, etc.) em que é possível se retomar a análise sintática com certa segurança; esses tokens precisam ser determinados pelo projetista do compilador while (x<2 do read(y)... Ao notar a falta do parênteses, relata-se a falta do mesmo e se consome tudo até que o token read seja encontrado, a partir de onde se recomeça a análise 6 3
Tratamento de erros Recuperação de frases (correção local) Ao se detectar um erro, realizam-se correções locais na entrada Por exemplo, substituição de vírgula por ponto e vírgula, remover um ponto e vírgula estranho ou inserir um Planejamento das correções possíveis pelo projetista do compilador Atenção: pode gerar um ciclo infinito! Inserem-se símbolos infinitamente 7 Tratamento de erros Produções de erro Com um bom conhecimento dos erros que podem ser cometidos, pode-se aumentar a gramática da linguagem com produções para reconhecer as produções ilegais e tratá-las adequadamente Correção global Idealmente, o compilador deveria fazer tão poucas modificações no programa quanto possível Escolhe-se uma seqüência mínima de modificações no programa que o tornem correto com o menor custo possível Método muito custoso de se implementar; apenas de interesse teórico 8 4
Modo de pânico: pulam-se tokens até que se encontre um a partir do qual se possa retomar a análise program P; var x: integer;... end. Diante da ausência/erro de var, de onde recomeçar a análise? Quem é esse símbolo de recomeço? Próximo id, seguidor do terminal var 9 Modo de pânico: pulam-se tokens até que se encontre um a partir do qual se possa retomar a análise program P; var x: integer;... end. Diante da ausência/erro de um trecho grande de código, de onde recomeçar a análise? Quem é esse símbolo de recomeço? Símbolo de, seguidor do não terminal de declaração de variáveis 10 5
Modo de pânico: pulam-se tokens até que se encontre um a partir do qual se possa retomar a análise program P;... read(x); write(x+2);... end. Diante da ausência/erro do ponto e vírgula (;), de onde recomeçar a análise? Quem é esse símbolo de recomeço? Símbolo de write, primeiro de comando 11 Modo de pânico: pulam-se tokens até que se encontre um a partir do qual se possa retomar a análise Símbolos de sincronização para um símbolo qualquer A sendo consumido Inicialmente, utilizam-se os seguidores(a) Acrescentam-se os símbolos seguidores do pai de A, isto é, de quem o acionou Útil quando tudo dentro de A deu errado Símbolos de sincronização extras, para garantir que muito da entrada não seja consumido Determinados pelo projetista do compilador 12 6
<comandos>::=<cmd>;<comandos> λ <cmd> ::= read... write... while (<condicao>) do <cmd> if... Se acontecer um erro dentro de <condição>, buscam-se seus seguidores para se retomar a análise: ), por exemplo 13 <comandos>::=<cmd>;<comandos> λ <cmd> ::= read... write... while (<condicao>) do <cmd> if... Se acontecer um erro dentro de <condição> e o programador omitiu os seguidores deste, buscam-se os seguidores do pai para continuar a análise: ponto e vírgula (;), por exemplo 14 7
<comandos>::=<cmd>;<comandos> λ <cmd> ::= read... write... while (<condicao>) do <cmd> if... Ao se buscar pelos seguidores, pode-se perder a análise de grande parte do programa nesse caso (a análise de <comandos>, por exemplo). Em pontos críticos do programa, buscam-se, portanto, tokens a partir de onde seja seguro retomar a análise: read, write e if, por exemplo 15 Nos analisadores descendentes preditivos, a busca por tokens de sincronização é feita sempre que um token esperado não é encontrado Análise recursiva: nos pontos de ERRO dos procedimentos recursivos Análise não recursiva: quando não é possível seguir em frente com a análise, por falta de produção na tabela sintática ou por incompatibilidade de terminais na pilha e na cadeia de entrada 16 8
ASD preditiva recursiva: tratamento de erros Algoritmo do procedimento ERRO procedimento ERRO(num_erro, conjunto_simb_sincr) imprimir mensagem de erro relativa ao erro de número num_erro; enquanto (token_corrente não pertence a conjunto_simb_sincr) faça obter_símbolo(); Observações Tomar o cuidado de verificar quando se chegou ao fim do programa-fonte Opcionalmente, a mensagem de erro pode ser impressa antes da chamada do procedimento ERRO Verificar quem é o seguidor encontrado 17 ASD preditiva recursiva: tratamento de erros <programa> ::= program id ; <corpo>. procedimento programa(s) se (simb=program) então obter_símbolo() imprimir( Erro: program esperado ); ERRO({id}+S); se (simb=id) então obter_símbolo() imprimir( Erro: identificador de programa esperado ); ERRO({;}+S); se (simb=simb_pv) então obter_símbolo() imprimir( Erro: ponto e vírgula esperado ); ERRO(Primeiro(corpo)+S); corpo({.}+s); se (simb=ponto) então obter_símbolo() imprimir( Erro: ponto esperado ); Atenção: se id encontrado, continua a análise normalmente; caso contrário, se elemento de S encontrado, deve-se sair desse procedimento Pode-se passar como um ou mais parâmetros 18 9
ASD preditiva recursiva: tratamento de erros <corpo> ::= <dc> <comandos> end procedimento corpo(s) dc({}+s); se (simb=) então obter_símbolo() imprimir( Erro: esperado ); ERRO(Primeiro(comandos)+S); comandos({end}+s); se (simb=end) então obter_símbolo() imprimir( Erro: end esperado ); ERRO(S); 19 ASD preditiva recursiva: tratamento de erros <cmd> ::= read(<variaveis>) write(<variaveis>)... procedimento cmd(s)... se (simb=simb_abre_parenteses) então obter_símbolo() imprimir( Erro: ( esperado ); ERRO(Primeiro(variaveis)+S+{read,write,while,if,id...});... 20 10
ASD preditiva recursiva: tratamento de erros Exercício: faça o procedimento recursivo com tratamento de erro <dc_v> ::= var <variaveis> : <tipo_var> ; <dc_v> 21 ASD preditiva recursiva: tratamento de erros Exercício: faça o procedimento recursivo com tratamento de erro <dc_v> ::= var <variaveis> : <tipo_var> ; <dc_v> procedimento dc_v(s) se (simb=var) então obter_símbolo() imprimir( Erro: var esperado ); ERRO(Primeiro(variaveis)+S); variaveis({:}+s); se (simb=simb_dp) então obter_símbolo() imprimir( Erro: : esperado ); ERRO(Primeiro(tipo_var)+S); tipo_var({;}+s); se (simb=simb_pv) então obter_símbolo() imprimir( Erro: ; esperado ); ERRO(Primeiro(dc_v)+S); dc_v(s); 22 11
ASD preditiva recursiva: tratamento de erros Outra estratégia de tratamento de erro: aninhamento de ifs <dc_v> ::= var <variaveis> : <tipo_var> ; <dc_v> procedimento dc_v(s) se (simb=var) então obter_símbolo(); variaveis({:}+s); se (simb=simb_dp) então obter_símbolo(); tipo_var({;}+s); se (simb=simb_pv) então obter_símbolo(); dc_v(s); imprimir( Erro: ; esperado ); ERRO(S); imprimir( Erro: : esperado ); ERRO(S); imprimir( Erro: var esperado ); ERRO(S); 23 ASD preditiva recursiva: tratamento de erros Outra estratégia de tratamento de erro: verificação de erro ao fim <dc_v> ::= var <variaveis> : <tipo_var> ; <dc_v> procedimento dc_v(s) se (simb=var) então obter_símbolo() imprimir( Erro: var esperado ); variaveis({:}+s); se (simb=simb_dp) então obter_símbolo() imprimir( Erro: : esperado ); tipo_var({;}+s); se (simb=simb_pv) então obter_símbolo() imprimir( Erro: ; esperado ); dc_v(s); ERRO(S); 24 12
ASD preditiva recursiva: tratamento de erros Outra opção No início de cada procedimento, verificação da presença do símbolo esperado Detecção mais cedo de erros Decisão sobre implementação Quais e quantos erros serão detectados e relatados Lógica de programação Controle 25 13