hoc2: variáveis e tratamento de erros
Aqui está descrito o programa do diretório etapa2/.
Explicação do programa
Nesta etapa 2 vamos implementar variáveis e o operador de atribuição =
na linguagem hoc
. Para reduzir as mudanças necessárias, por enquanto vamos suportar variáveis com nomes de uma letra apenas, e somente minúsculas de “a” até “z”.
O operador de atribuição terá associatividade direita, e formará uma expressão cujo valor é o número que está sendo atribuído. Essas características permitem atribuições múltiplas, assim:
a = b = c = 3
Usaremos variáveis de “a” até “z” porque assim é fácil transformar o nome em um índice para um array de 26 posições. Introduzir variáveis na gramática traz uma complicação: agora a pilha de execução de yacc precisará lidar com valores de dois tipos: double
para os números, e int
para as variáveis. Para isso usaremos uma declaração %union
no prólogo.
Também vamos melhorar o tratamento de erros. Em hoc1b
, uma expressão com erro de sintaxe como 3+/2
encerra o interpretador. Em hoc2
vamos mostrar um aviso e continuar.
✋ No texto original de UPE, os autores comentam que a versão
hoc1
também encerra quando ocorre uma divisão por zero. Não consegui reproduzir este erro. Quando digito1/0
emhoc1b
aparece o resultadoinf
, e para-1/0
, o resultado é-inf
— que correspondem a ∞ e -∞ pela norma IEEE 754-1985.
Mudanças no prólogo
O prólogo de hoc2.y
fica assim entre as marcas %{
e %}
— aqui omitidas para não atrapalhar a colorização da sintaxe na Web:
#include <stdio.h>
#include <ctype.h>
#include <signal.h> /* ❶ */
#include <setjmp.h>
double mem[26]; /* ❷ memória para variáveis 'a'...'z' */
int yylex(void);
void yyerror(char *);
void aviso(char *s); /* ❸ */
void recuperar(char* s);
- Inclusão das bibliotecas
signal.h
esetjmp.h
que usaremos para tratar erros. - Declaração do array de 26 posições para as variáveis.
- Declaração das funções
aviso
erecuperar
, implementadas no final do arquivo.
Mudanças nas declarações de yacc
Após o final do prólogo marcado por %}
temos várias novidades nas delcarações de yacc:
%}
%union { /* ❶ tipo da pilha de yacc */
double val; /* valor numérico */
int indice; /* indice para acessar mem[] */
}
%token <val> NUMERO /* ❷ */
%token <indice> VAR /* ❸ */
%type <val> expr /* ❹ */
%right '=' /* ❺ associatividade direita */
%left '+' '-' /* associatividade esquerda */
%left '*' '/'
%left NEGATIVO
- A pilha de yacc agora vai conter elementos definidos pela união desses dois tipos:
double
quando for um valor numérico, ouint
quando for uma variável representada pelo índice no arraymem[]
. - A declaração da categoria de token
NUMERO
agora inclui o tipo<val>
, que se refere ao membro tipodouble
da união declarada acima. - A nova declaração da categoria
VAR
inclui o tipo<indice>
, indicando o membroint
na união. - Esta declaração indica que o tipo de uma expressão é
<val>
, o mesmo quedouble
na união. - O token
=
é declarado com precedência mínima e associatividade direita.
Mudanças na gramática
A gramática, como sempre declarada entre %%
, agora fica assim:
%%
lista: /* nada */
| lista '\n'
| lista expr '\n' { printf("\t%.8g\n", $2); }
| lista error '\n' { yyerrok; } /* ❶ */
;
expr: NUMERO { $$ = $1; }
| VAR { $$ = mem[$1]; } /* ❷ */
| VAR '=' expr { $$ = mem[$1] = $3; } /* ❸ */
| '-' expr %prec NEGATIVO { $$ = -$2; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { /* ❹ */
if ($3 == 0.0)
recuperar("division by zero");
$$ = $1 / $3; }
| '(' expr ')' { $$ = $2; }
;
%%
/* fim da gramática */
- Esta nova regra usa a palavra
error
que tem um significado especial em uma gramática yacc. Ela serve para indicar que estados de erro poderão acontecer e serão tratados, em vez de encerrar o programa. - Quando a expressão se reduz a uma variável (que é um índice
int
), seu valor é obtido acessando a posição correspondente emmem[]
. - Em uma expressão de atribuição, o efeito é colocar o valor da
expr
à direita na posição demem[]
que corresponde ao índice da variável. - A ação associada à divisão agora faz um teste: se o denominador tem valor 0.0, o parser desvia para a função
recuperar
com uma mensagem de divisão por zero. Isso evita os resultadosinf
e-inf
que tínhamos emhoc1b
.
Mudanças no epílogo
O código em C após o fim da gramática tem várias novidades. Primeiro, as variáveis globais e a função principal:
char *nome_prog; /* para mensagens de erro */
int num_linha = 1;
jmp_buf inicio; /* ❶ dados para longjmp */
int main(int argc, char* argv[]) /* hoc2 */
{
void tratar_exc_pf();
nome_prog = argv[0];
setjmp(inicio); /* ❷ */
signal(SIGFPE, tratar_exc_pf); /* ❸ */
yyparse();
}
- A variável
inicio
armazenará uma struct com dados para o funcionamento das chamadassetjmp
elongjmp
que servirão para reiniciar o interpretador em caso de erro ou exceção. - A chamada
setjmp(inicio)
armazena informações sobre este ponto do programa para permitir o desvio para este ponto quandolongjmp
for invocada na funçãorecuperar
, definida mais abaixo. Na prática,setjmp
marca um alvo, ou destino, para um desvio de execução. - Essa chamada registra a função
tratar_exc_pf
como handler (tratadora) para quando o sistema operacional levantar um sinalSIGFPE
que é uma exceção de ponto flutuante usada, por exemplo, para indicar overflow.
✋ Não consegui reproduzir a exceção de overflow citada no texto original de UPE. Quando digito
1e300*1e300
emhoc2
aparece o resultadoinf
— o ∞ da norma IEEE 754-1985. Se você sabe como provocar uma exceção de ponto flutuante emhoc2
, por gentileza faça um pull-request, pois assim poderemos demonstrar ou uso designal
.
A função de análise léxica ganha mudanças no acesso à variável yylval
, mais algumas linhas para tratar um token de variável:
int yylex(void) /* hoc2 */
{
int c;
while ((c=getchar()) == ' ' || c == '\t')
;
if (c == EOF)
return 0;
if (c == '.' || isdigit(c)) { /* número */
ungetc(c, stdin);
scanf("%lf", &yylval.val); /* ❶ */
return NUMERO;
}
if (islower(c)) { /* ❷ */
yylval.indice = c - 'a'; /* só ASCII */
return VAR; /* ❸ */
}
if (c == '\n')
num_linha++;
return c;
}
- Agora
yyval
não é mais um valor simples, e sim uma união de dois membros. Aqui atribuímos o valor numérico lido ao membto.val
. - Esse novo
if
testa sec
é um caractere ASCII minúsculo. Em caso afirmativo,yyval.indice
recebe o valor dec
menos'a'
(o código ASCII do “a” minúsculo). Por exemplo, o índice da variável'a'
será 0,'b'
será 1, etc. - Depois de armazenar o índice em
yylval
, devolvemos para o parser a indicação de que um token da categoriaVAR
.
E finalmente, temos as funções de tratamento de erros:
void yyerror(char* s) /* erro de sintaxe */
{
aviso(s);
}
void aviso(char *s) /* exibir aviso */
{
fprintf(stderr, "%s: %s near line %d\n",
nome_prog, s, num_linha);
}
void recuperar(char* s) /* recuperar de um erro de uso */
{
aviso(s);
longjmp(inicio, 0);
}
void tratar_exc_pf() /* tratar exceções de ponto flutuante */
{
recuperar("floating point exception");
}
yyerror
, que é chamada pelo parser gerado por yacc, agora usa nossa funçãoaviso
.aviso
apenas exibe uma mensagem emstderr
, informando a linha onde o erro foi detectado.recuperar
exibe um aviso, e desvia a execução para o ponto marcado pela chamadasetjmp(inicio)
na funçãomain
.tratar_exc_pf
usa recuperar para indicar uma exceção de ponto flutuante.
✋
tratar_exc_pf
é a função que não consegui testar, porque não consegui gerar uma exceção que gere o sinalSIGFPE
.
Construir e testar
Use make
para gerar o código em C e compilar:
$ make hoc2
yacc hoc2.y
mv -f y.tab.c hoc2.c
cc -c -o hoc2.o hoc2.c
cc hoc2.o -o hoc2
rm hoc2.o hoc2.c
Para testar, use o arquivo testes.hoc
. Este é o resultado esperado:
$ ./hoc2 < testes.hoc
4
-7
14
0
37.777778
100
./hoc2: division by zero near line 7
100
O arquivo de testes agora tem este conteúdo:
a = 2 + 2
-3 - a
x = y = z = 2 + 3 * 4
-x - y * 2 + z * 3
c = (100 - 32) * 5 / 9
f = 32 + c * 9 / 5
1/0
f
Além de exercitar as variáveis e expressões de atribuição, na penúltima linha há uma divisão por zero. Isso demonstra que o interpretador exibe a mensagem corretamente, e se recupera, inclusive mantendo o valor da variável f que é o resultado da última linha: 100.
Voltar para o índice de páginas.