Carregar apresentação
A apresentação está carregando. Por favor, espere
1
Capítulo VI Análise Semântica
CES-41 COMPILADORES Capítulo VI Análise Semântica
2
Capítulo VI – Análise Semântica
6.1 – Classificação dos testes semânticos 6.2 – Montagem de uma tabela de símbolos 6.3 – Testes semânticos no Yacc 6.4 – Testes semânticos em análise preditora 6.5 – Formalização de análise semântica
3
6.1 – Classificação dos Testes Semânticos
6.1.1 – O papel da análise semântica A análise semântica verifica se as construções sintáticas fazem sentido GLC’s não são suficientemente poderosas para descrever várias construções de linguagens de programação É conveniente que aspectos dependentes de contexto nessas linguagens sejam tratados durante a análise semântica
4
Exemplo: seja a linguagem já vista no Capítulo II:
L = {w c w | w (a | b)*} onde ∑ = {a, b, c} Sentença típica: aababcaabab A linguagem é uma simplificação daquelas que exigem a declaração de todo identificador usado nos comandos executáveis. Tais linguagens não podem ser geradas por gramáticas livres de contexto (GLC’s)
5
Exemplo 6.2: Um teste dinâmico: Sejam a declaração e os comandos em C:
Os testes semânticos são de caráter estático, em contraposição aos testes dinâmicos feitos em tempo de execução Exemplo 6.2: Um teste dinâmico: Sejam a declaração e os comandos em C: int A[100], i, x; scanf (“%d”, &i); x = A[i]; Evitar i > 99, só é possível em tempo de execução
6
Os testes semânticos podem ser grupados em quatro classes:
Declaração e uso de identificadores e constantes Compatibilidade de tipos Fluxo de dados Fluxo de controle
7
6.1.2 – Declarações e uso de identificadores e constantes
a) Todos os identificadores usados devem ser declarados: A verificação não é tão simples em linguagens organizadas por blocos ou linguagens que admitem aninhamento de sub-programas
8
Exemplo: em C: É exigida informação sobre o escopo de validade de um identificador na TabSimb int x ( ) { int a; } int y ( ) { . . . = a } A variável a está na TabSimb Mas seu uso em y está incorreto
9
Declaração de a no Bloco 1
Exemplo: em C: // Declaracoes globais void main ( ) { { //Bloco 1 int a; {//Bloco 2 {//Bloco 3 = a ; }//Bloco 3 - fim }//Bloco 2 - fim }//Bloco 1 – fim {//Bloco 4 = a ; }//Bloco 4 - fim }// main - fim Declaração de a no Bloco 1 Uso correto Uso incorreto
10
Nessas linguagens, a tabela de símbolos deve ter informações sobre o escopo de validade de um identificador Fortran dispensa a declaração dos identificadores usados
11
b) Dupla declaração de identificadores:
Isso é proibido para variáveis, funções e tipos globais ou locais de um mesmo bloco A Linguagem C permite que o nome de uma função seja usado também como nome de variável local a essa função É que a função é definida no escopo global No entanto, uma função não pode ter o mesmo nome de uma variável global
12
Exemplo: em C: #include <stdio.h> Declarações legais de a
int a = 7; int b ( ) { int b = 3; return a+b; } float x ( ) { float a = 8.7; return a; } void main ( ) { char a = '$'; int c; float d; c = b( ); d = x( ); printf ("a = %c, c = %d, d = %g", a, c, d); } Declarações legais de a Declarações legais de b
13
Exemplo: em C: Duas declarações de a: ambas no escopo global
#include <stdio.h> int a = 7; int a ( ) { int b = 3; return a+b; } float x ( ) { float a = 8.7; return a; } void main ( ) { char a = '$'; int c; float d; c = b( ); d = x( ); printf ("a = %c, c = %d, d = %g", a, c, d); } Duas declarações de a: ambas no escopo global A segunda declaração é ilegal
14
c) Dimensionamento de variáveis indexadas:
Cada linguagem tem sua forma de dimensionar Pascal: ID : array [CTE CTE2] of TipoPrimitivo ; É obrigatório que CTE1 CTE2 Linguagem C: Tipo Variável [ CTE ] ; É obrigatório que CTE > 0
15
d) Uso de subscritos: Variáveis escalares não podem ter subscritos Expressões e lados esquerdos de comandos de atribuição em C: O número de subscritos de variáveis indexadas e de campos indexados de estruturas deve ser igual ao número de suas dimensões
16
Referências legais à variável A Referências ilegais à variável A
Exemplo: código em C: int i, j, k, A[10][20][5]; A[i+1][2-j][3*k] = ; = A[7][15][2] ; A[i-3] = ; ..... = A[7][15] ; A = ; Referências legais à variável A Referências ilegais à variável A
17
Comandos de leitura em C:
O número de subscritos de variáveis indexadas do tipo char e de campos indexados desse tipo nas estruturas deve ser igual ou uma unidade menor que o número de suas dimensões Exemplo: código legal em C: char A[15], B[5][10]; scanf (“%s%s%c%c”, A, B[4], A[12], B[2][3]);
18
Comandos de escrita em C:
Pode-se escrever um vetor de elementos de uma variável indexada do tipo char ou do campo indexado desse tipo de uma estrutura Exemplo: código legal em C: char A[15], B[5][10]; printf (“A = %s; B[4] = %s”, A, B[4]);
19
e) Campos de estruturas e de union’s:
Estão usados corretamente, isto é, correspondem a um campo declarado da estrutura na qual está sendo usado? Exemplo: typedef struct st st; struct st {int a; float x;}; st s; s.a = ; x = ; correto incorreto
20
Pascal não admite que, num mesmo escopo, o identificador de um campo de uma estrutura seja usado como campo de outra estrutura ou como nome de variável Em Pascal: type st1 = record a: integer; b: real; end; st2 = record a: real; c: char; var a: integer; Declarações ilegais Em C: struct st1 { int a; float b; }; struct st2 { float a; char c; int a; Declarações legais
21
f) Unicidade dos rótulos de um comando de seleção
Exemplo: comando switch-case em C: switch (expr) { case 1: ; break; case 2: ; break; } g) Declaração de um único módulo principal Erro: rótulo repetido
22
6.1.3 – Compatibilidade de tipos
a) Já vistos em aulas de laboratório: Compatibilidade entre operadores e operandos de expressões Compatibilidade entre os dois lados de um comando de atribuição Compatibilidade da expressão de controle dos comandos condicionais, repetitivos e da expressão em subscritos de variáveis indexadas
23
b) Compatibilidade entre argumentos de chamada e parâmetros de um sub-programa:
Essa compatibilidade deve ser em número, tipo e modo de passagem de parâmetros Em passagem por valor, o argumento deve ser uma expressão; em passagem por referência, deve ser uma variável c) Compatibilidade entre o valor retornado e o tipo de uma função d) Compatibilidade entre o protótipo e a definição de um subprograma
24
6.1.4 – Fluxo de dados a) Inicialização e referência a variáveis
Já vistos em aulas de laboratório b) Alteração na variável de controle de laços: Em algumas linguagens como Pascal, Algol, Fortran, etc., os laços for vêm com uma variável de controle explícita Em algumas delas, é proibido alterar seu valor no escopo do laço
25
Exemplo: em Pascal: for i := 1 to n do begin - - - - - i := ..... ;
read (i); end proibidos
26
c) Utilidade de valores atribuídos às variáveis
Não raro, programas atribuem valores inúteis a algumas de suas variáveis, ou seja, valores que não serão usados em nenhum momento em qualquer execução do programa A detecção desse fato normalmente ocorre na fase de otimização do código intermediário, por meio de uma análise de fluxo de dados do programa
27
6.1.5 – Fluxo de controle a) Detecção de sub-programas recursivos
b) Detecção de chamadas recursivas infinitas c) Um escape de comando composto está dentro de um comando composto? d) Um comando será executado em alguma circunstância? Exigem análise do fluxo de controle, normalmente aplicada ao programa na fase de otimização do código intermediário
28
6.2 – Montagem de uma Tabela de Símbolos
6.2.1 – Abordagem progressiva: Linguagens sem subprogramas (visto em aulas de laboratório) Linguagens com subprogramas desaninhados e sem estruturas de blocos Linguagens com aninhamentos de subprogramas e/ou estruturas de blocos
29
6.2.2 – Linguagens sem subprogramas
Estrutura de dados em hashing aberto Função para o hashing: Onde: NCLASSHASH é o número de classes. n é o número de caracteres de x (sem o ‘\0’)
30
Esquema da tabela de símbolos:
31
typedef struct celsimb celsimb; typedef celsimb *simbolo;
Declarações: typedef struct celsimb celsimb; typedef celsimb *simbolo; struct celsimb { char *cadeia; int tid, tvar, ndims, dims[MAXDIMS+1]; char inic, ref, array; simbolo prox; }; simbolo tabsimb[NCLASSHASH]; int A[10][20][5]; A cadeia IDVAR tid INTEIRO tvar inic ref 1 array 3 ndims 10 20 5 dims 2
32
(char *cadeia, int tid, int tvar) int hash (char *cadeia)
Funções: int InicTabSimb ( ) simbolo ProcuraSimb (char *cadeia) simbolo InsereSimb (char *cadeia, int tid, int tvar) int hash (char *cadeia) A cadeia IDVAR tid INTEIRO tvar inic ref 1 array 3 ndims 10 20 5 dims 2
33
6.2.3 – Linguagens com subprogramas desaninhados e sem estruturas de blocos
Há vários tipos de subprogramas: Cada nome de subprograma deve ter informação sobre o tipo de subprograma Em Pascal, por exemplo, tid = IDFUNC ou IDPROC
34
Escopos para linguagens sem aninhamento de subprogramas
Escopo global Escopo de cada subprograma O escopo global abrange os dos subprogramas Escopo do programa principal: Em C, tem tratamento de subprograma Em Pascal, tem tratamento de escopo global
35
Informação sobre escopo de um identificador:
Ponteiro para a célula da tabela que contém o nome do identificador que define o escopo Exemplo: em C ff IDFUNC a IDVAR escopo int ff ( ) { int a; }
36
Há escopos que não correspondem a nenhum identificador
Exemplo: escopo global em C Pode-se usar na tabela um identificador artificial: ##global, por exemplo Tipo de id: IDGLOB Novas informações para identificadores de variáveis: Se são parâmetros ou não Tipo de passagem de parâmetros
37
Novas informações para identificadores de subprogramas ou do escopo global:
Lista de variáveis locais Lista de parâmetros Lista de funções (para o escopo global) Exemplo: a seguir, tabela de símbolos para o código int d, e, f; int ff (int m, int n) { int q, p; }
38
int d, e, f; int ff (int m, int n) { int q, p; }
39
Sugestões para declarações relativas à tabela de símbolos
/* Definicao dos tipos de identificadores */ #define IDGLOB 1 #define IDVAR 2 #define IDFUNC 3 #define IDPROC 4 #define IDPROG 5 /* Definicao dos tipos de passagem de parametros */ #define PARAMVAL 1 1 #define PARAMREF 2 2
40
/* Listas de simbolos */
typedef struct elemlistsimb elemlistsimb; typedef elemlistsimb *pontelemlistsimb; typedef elemlistsimb *listsimb; struct elemlistsimb { simbolo simb; pontelemlistsimb prox; }
41
/* Tabela de simbolos */
typedef struct celsimb celsimb; typedef celsimb *simbolo; struct celsimb { char *cadeia; int tid, tvar, tparam , ndims, dims[MAXDIMS+1]; char inic, ref, array, parametro ; listsimb listvar, listparam, listfunc; simbolo escopo , prox; }; simbolo tabsimb[NCLASSHASH];
42
Bloco { ListDeclarações ListComandos }
6.2.4 – Linguagens com aninhamentos de subprogramas e/ou estruturas de blocos Blocos são comandos compostos com declarações no início Numa gramática, um bloco pode ser implementado pela seguinte produção: Bloco { ListDeclarações ListComandos }
43
Numa gramática, subprogramas aninhados podem ser implementados pelas seguintes produções:
Programa CabeçalhoProg Declarações SubProgramas CmdComposto SubProgramas ε | SubProgramas DefSubProg DefSubProg CabeçalhoSubProg Declarações SubProgramas CmdComposto
44
Pascal trabalha com blocos e os subprogramas podem ser aninhados
A Linguagem C trabalha com blocos, mas os subprogramas são desaninhados
45
Em linguagens com essa estrutura, os escopos formam uma árvore hierárquica:
O escopo global é a raiz da árvore Os subprogramas do escopo global são seus filhos na árvore Os subprogramas e os blocos internos a um outro subprograma são filhos desse último Os campos de uma estrutura e de uma union são filhos dessas construções
46
Os escopos dos blocos podem ser representados na tabela de símbolos por identificadores artificiais (por exemplo, ##bloco01, ##bloco02, etc.) Cada identificador de escopo pode ter uma lista de escopos filhos, tal como o caso do escopo global, no passo anterior desta abordagem progressiva Dois identificadores na tabela de símbolos podem ter o mesmo nome, desde que pertencentes a escopos diferentes Assim sendo, uma procura na tabela de símbolos deve informar qual o identificador procurado e em qual escopo ela é feita
47
Exemplo: blocos em C: void main ( ) { {bloco 1} {bloco 2} }
int f1 ( ) { {bloco 3} {bloco 4} float f2 ( ) { struct st { int c1; float c2} ##global escopo listescfilhos main escopo listescfilhos ##bloco 1 escopo ##bloco 2 escopo f1 escopo listescfilhos f2 st escopo escopo ##bloco 3 escopo ##bloco 4 f2 escopo listtipos c1 escopo c2 escopo st
48
6.3 – Testes Semânticos no Yacc
6.3.1 – Linguagem ilustrativa A linguagem para ilustrar este tópico tem as seguintes características, no tocante a subprogramas e blocos: Os subprogramas são do tipo função, podendo ser eles do tipo void (que não retornam valor) ou de algum tipo (retornando valor escalar) Não há aninhamento de funções Internamente às funções, pode haver aninhamentos de blocos
49
Produções da gramática, para subprogramação:
Prog : DeclGlobs Funções DeclGlobs : ε | globais : ListDecls ListDecls : (conhecidas de outras gramáticas) Funções : funcoes : ListFunc ListFunc : Função | ListFunc Função Função : Cabeçalho Bloco Cabeçalho : Tipo ID ( ListParam ) Tipo : int | | void ListParam : (conhecidas de outras gramáticas) Átomos em negrito
50
Exemplo: um programa típico:
Bloco : {{ DeclLocs Comandos }} DeclLocs : ε | locais : ListDecls Comandos : comandos : ListCmds ListCmds : Comando | ListCmds Comando Comando : CmdAtrib | CmdSe | | Bloco | CmdComposto CmdComposto: { ListCmds } Exemplo: um programa típico: globais: int a, b; real c, d; funcoes: Delimitadores de blocos (ABBLOC e FBLOC)
51
c = d + 1; if ( - - - - - ) - - - - - ; m = !n; }
int ff (int x, y;) {{ locais: logic m, n; comandos: a = b + 1; { c = d + 1; if ( ) ; m = !n; } {{ locais: int k, l; comandos: }} }} void main ( ) {{ locais: comandos: Delimitadores de blocos (ABBLOC e FBLOC)
52
Variável : ID Subscritos
6.3.2 – Declaração e uso de identificadores É necessário verificar se o identificador usado está na tabela de símbolos, no escopo de seu uso ou em algum ancestral desse escopo na árvore de escopos A função ProcuraSimb deve ter como parâmetro, além da cadeia procurada, o escopo onde ela deve ser procurada Essa procura deve ser feita na produção Variável : ID Subscritos
53
Exemplo: escopo /* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl 5 */ }} ##global escopo main escopo ##bl 1 escopo ##bl 2 escopo ##bl 3 escopo átomo em análise ##bl 4 escopo escopo Escopo do uso de a ff escopo ##bl 5 escopo Usar uma variável global escopo para indicar o escopo do átomo em análise
54
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo escopo No início, insere-se o identificador ##global na TabSimb, que passa a ser o escopo corrente
55
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo escopo O identificador main é inserido na TabSimb, no escopo de ##global
56
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo escopo O escopo corrente passa a ser o de main
57
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo O identificador ##bl1 é inserido na TabSimb, no escopo de main
58
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo O escopo corrente passa a ser o de ##bl1
59
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo O escopo corrente volta a ser o de main
60
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo ##bl2 escopo O identificador ##bl2 é inserido na TabSimb, no escopo de main
61
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo ##bl2 escopo O escopo corrente passa a ser o de ##bl2
62
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo ##bl2 escopo ##bl3 escopo O identificador ##bl3 é inserido na TabSimb, no escopo de ##bl2
63
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo ##bl2 escopo ##bl3 escopo O escopo corrente passa a ser o de ##bl3
64
/* Declaracoes globais */ - - - - - void main ( ) {{
{{ /*bl 1*/ }} {{ /*bl 2*/ {{ /*bl 3*/ . . . = a }} {{ /*bl 4*/ }} int ff ( ) {{ {{ /*bl */ }} ponto da análise ##global escopo main escopo ##bl1 escopo escopo ##bl2 escopo ##bl3 escopo O escopo corrente é o de ##bl3 O identificador a é procurado primeiramente no escopo de ##bl3 Depois no de ##bl2, de main e de ##global Não sendo achado, não está declarado
65
1) Inserindo o identificador ##global:
Programação em Yacc: 1) Inserindo o identificador ##global: Prog : {escopo = InsereSimb (“##global”, IDGLOB, NAOVAR, NULL);} DeclGlobs Funções Neste ponto, o escopo corrente é o de ##global As variáveis globais e as funções serão inseridas nesse escopo
66
2) Entrada e saída do escopo de uma função:
Cabeçalho : Tipo ID {escopo = InsereSimb ($2, IDFUNC, tipocorrente, escopo);} ABPAR Listparam FPAR ; Neste ponto, o escopo corrente é o do nome da função Os parâmetros serão inseridos nesse escopo Função : Cabeçalho {proxblocoehfuncao = VERDADE} Bloco ; Para indicar, que o bloco a seguir é de função
67
3) Entrada e saída do escopo de um bloco: Criação do tid = IDBLOC
#define IDGLOB 1 #define IDVAR 2 #define IDFUNC 3 #define IDPROC 4 #define IDPROG 5 #define IDBLOC 6 Função para gerar o nome de um novo bloco: NovoBloco Retorna um ponteiro para uma cadeia de caracteres
68
Chamadas do não-terminal Bloco
Comando : CmdAtrib | CmdSe | | Bloco | CmdComposto Função : Cabeçalho Bloco
69
Prepara para receber blocos embutidos no bloco corrente
Bloco : ABBLOC { if (! proxblocoehfuncao) escopo = InsereSimb (NovoBloco (), IDBLOC, NAOVAR, escopo); } DeclLocs {proxblocoehfuncao = FALSO;} Comandos FBLOC {escopo = escopo->escopo;} ; Só cria nome para bloco e entra em seu escopo, se o bloco não for o corpo de uma função Prepara para receber blocos embutidos no bloco corrente Saindo de um bloco, o escopo corrente é o do pai
70
4) Teste de declaração de identificadores usados
Variável : ID { escaux = escopo; simb = ProcuraSimb ($1, escaux); while (escaux && !simb) { escaux = escaux->escopo if (escaux) } if (! simb) NaoDeclarado ($1); else if (simb->tid != IDVAR) TipoInadequado ($1); } Subscritos ; ##global escopo main escopo ##bl1 escopo ##bl2 escopo ##bl3 escopo escopo
71
ElemDecl : ID { 5) Teste de dupla declaração de identificadores
Basta verificar se o identificador recém-declarado já está na tabela de símbolos, no escopo corrente ElemDecl : ID { if (ProcuraSimb ($1, escopo)) DuplaDeclaracao ($1); } ;
72
6.3.3 – Variável de controle de comandos for:
Produção: CmdFor: for ABPAR Variavel ATRIB Expressao PVIRG Expressao PVIRG Variavel ATRIB Expressao FPAR Comando O tipo da primeira Variavel e da primeira e terceira Expressao deve ser escalar inteiro ou caractere O tipo da segunda Expressão deve ser lógico A segunda Variavel deve ser igual à primeira
73
Produções simplificadas para o comando for em Pascal:
CmdFor : for CmdAtrib to Expressao do Comando CmdAtrib : ID = Expressao Funcionamento desse comando em termos de comandos while A variável i é chamada de variável de controle do for
74
Testes semânticos cabíveis:
CmdFor : for CmdAtrib to Expressao do Comando CmdAtrib : ID = Expressao Testes semânticos cabíveis: A variável de controle deve ser escalar do tipo inteiro ou caractere (testes semelhantes já foram apresentados) As expressões dessas produções devem ser do tipo inteiro ou caractere (testes semelhantes já foram apresentados) A variável de controle não pode ser global (verificar escopo) A variável de controle não pode ser alterada no escopo desse comando (ver programa a seguir)
75
Exemplo de programa com variável de controle global:
/* Declaracoes globais */ int i; funcoes: void main ( ) {{ int n; for i := 1 to n do call ff (); }} int ff ( ) {{ i := ; A chamada de ff dentro do comando for altera o valor de i (variável de controle - global) Deve-se verificar qual o escopo de i, por ela ser a variável de controle do comando for
76
Seja a seguinte gramática simplificada:
Prog : CmdComposto CmdComposto: { ListCmds } ListCmds : Comando | ListCmds Comando Comando : CmdComposto | CmdFor | CmdAtrib CmdFor : for CmdAtrib to Expressao do Comando CmdAtrib : ID = Expressao Expressao : Termo | Termo + Termo Termo : ID | CTINT O programa a seguir implementa o teste semântico das alterações da variável de controle do for em seu escopo Pode haver aninhamentos de for’s o que torna o teste mais complexo
77
%{ #include <string.h> typedef struct noh noh; typedef noh *pilha; struct noh { char elem[30]; noh *prox; }; pilha PFor; void Empilhar (char *, pilha *); void Desempilhar (pilha *); char *Topo (pilha); void InicPilha (pilha *); char Vazia (pilha); noh *Procurar (char *, pilha); %} Será usada uma pilha para guardar as variáveis de controle de um aninhamento de comandos for
78
%union { char cadeia[50]; int valint; char carac; } %type <cadeia> CmdAtrib %token <cadeia> ID %token <valint> CTINT %token ABCHAV %token FCHAV %token ATRIB %token MAIS %token FOR %token TO %token DO %token <atr> INVAL %% CmdAtrib terá o mesmo atributo de sua variável do lado esquerdo
79
Prog : {InicPilha (&PFor);} CmdComposto
CmdComposto: ABCHAV {printf ("{\n");} ListCmds FCHAV {printf ("}\n");} ListCmds : Comando | ListCmds Comando Comando : CmdComposto | CmdFor | CmdAtrib {printf ("\n");} No começo, a pilha é inicializada vazia
80
CmdFor: FOR {printf ("for ");}
CmdAtrib {Empilhar ($3, &PFor);} TO {printf ("to ");} Expressao DO {printf ("do\n");} Comando {Desempilhar (&PFor);} ; Processado CmdAtrib, seu atributo, ou seja, a variável de controle é empilhada Encerrado o processamento de CmdFor, a variável de controle é desempilhada
81
CmdAtrib : ID { printf ("%s ", $1); if (Procurar ($1, PFor)) printf ( "** Alteracao em variavel de controle de for: %s ** ", $1); } ATRIB {printf ("= ");} Expressao {strcpy ($$, $1);} ; A variável do lado esquerdo de CmdAtrib é procurada na pilha Caso seja encontrada: erro O atributo de ID é copiado no atributo de CmdAtrib
82
Expressao : Termo | Termo MAIS {printf ("+ ");} Termo Termo : ID {printf ("%s ", $1);} | CTINT {printf ("%d ", $1);} ; %% #include "lex.yy.c" Definições das funções para manipular a pilha de variáveis de controle
83
Exemplo: seja um arquivo de entrada com o seguinte programa:
{ i = 1 for j = 2 to 10 do { k = b = e+ww for i = d+f to h+22 do j = 5 v = 3 for k = 32+i to n+2 do { i = 3 i = 4+b for k = 3 to 20 do { j = 3 k = i+w }
84
Saída { i = 1 for j = 2 to 10 do k = 2 + 3 b = e + ww
for i = d + f to h + 22 do j ** Alteracao em variavel de controle de for: j ** = 5 v = 3 for k = 32 + i to n + 2 do i = 3 i = 4 + b for k ** Alteracao em variavel de controle de for: k ** = 3 to 20 do j ** Alteracao em variavel de controle de for: j ** = 3 k ** Alteracao em variavel de controle de for: k ** = i + w } Saída
85
Observações: As variáveis de um comando read também devem ser procuradas na pilha de variáveis de controle Linguagens com aninhamentos de blocos e/ou de funções podem tornar o teste mais complexo (o corpo do for pode ser um bloco) A pilha do for pode exigir o escopo da declaração da variável de controle
86
6.3.4 – Unicidade dos rótulos de um comando de seleção
Exemplo de um comando de seleção (Pascal-like): case ( expr ) { ct1, ct2, ct3: Cmd1 ct4, ct5: Cmd2 ct6, ct7, ct8: Cmd3 ct9: Cmd4 default: Cmd5 } Entre as constantes inteiras ct1, ct2, ct3, ct4, ct5, ct6, ct7, ct8 e ct9 não deve haver repetições
87
Possíveis produções para um comando de seleção:
CmdCase CASE ABPAR ExprArit FPAR ABCHAV ListCase FCHAV ListCase ListCaseSimpl AlterDefault ListCaseSimpl ListAlterCase DPONTS Comando | ListCaseSimpl ListAlterCase DPONTS Comando ListAlterCase AlterCase | ListAlterCase VIRG AlterCase AlterCase CTINT | CTCHAR AlterDefault | DEFAULT DPONTS Comando case ( expr ) { ct1, ct2, ct3: Cmd1 ct4, ct5: Cmd2 ct6, ct7, ct8: Cmd3 ct9: Cmd4 default: Cmd5 } Idéia: usar listas de rótulos como atributos de alguns desses não-terminais
88
Exemplo: case (Expressao) { 1, 2, 3: Comando 4, 5: Comando 6: Comando
} Em algumas reduções, concatena-se listas de rótulos Em outras, inaugura-se uma lista dessas
89
Acrescenta-se o atributo listrotcase na %union
Acrescenta-se a seguinte declaração %type: %type <listrotcase> ListCase ListCaseSimpl ListAlterCase AlterCase Nas produções do não-terminal AlterCase: AlterCase : CTINT { $$ = InicListCase ($1)} | CTCHAR { $$ = InicListCase ($1)} ; O não terminal AlterCase terá sempre uma lista contendo um rótulo
90
Nas produções do não-terminal ListAlterCase:
ListAlterCase : AlterCase | ListAlterCase VIRG AlterCase {$$ = ConcatListCase ($1, $3);} ; Na primeira produção, a ação default é {$$ = $1}; a lista do não-terminal AlterCase será passada para o não-terminal da esquerda Na segunda produção, as listas dos não-terminais ListAlterCase e AlterCase serão concatenadas e passadas para o não terminal da esquerda
91
Nas produções do não-terminal ListCaseSimpl :
ListCaseSimpl : ListAlterCase DPONTS Comando | ListCaseSimpl ListAlterCase DPONTS Comando {$$ = ConcatListCase ($1, $2);} ; Na primeira produção, a ação default é {$$ = $1}; a lista do não-terminal ListAlterCase será passada para o não-terminal da esquerda. Na segunda produção, as listas dos não-terminais ListCaseSimpl e ListAlterCase serão concatenadas e passadas para o não-terminal da esquerda
92
Na produção do não terminal ListCase:
ListCase : ListCaseSimpl AlterDefault A ação default é {$$ = $1}; a lista do não-terminal ListCaseSimpl será passada para o não-terminal da esquerda Na produção do não-terminal CmdCase: CmdCase : CASE ABPAR ExprArit FPAR ABCHAV ListCase FCHAV { ChecUnicRotCase ($6); } ; A lista do não-terminal ListCase será examinada para verificar se possui algum rótulo repetido
93
6.3.5 – Testes semânticos relacionados com ponteiros
1) Produção para a declaração de ponteiros: ElemDecl : ID Dimens | @ ID O caractere declara que ID é um ponteiro Aqui ID no lugar de * ID para evitar duplicidade de uso do caractere ‘*’
94
2) Produções para referências a ponteiros:
Fator : Variavel | CTINT | CTREAL | CTCHAR | TRUE | FALSE | ~ Fator | ( Expressao ) | CallFunc | # Variavel CmdAtrib : LadoEsquerdo := Expressao ; | LadoEsquerdo := & Variavel ; LadoEsquerdo: Variavel | # Variavel # Variavel significa “local apontado por Varíavel” & Variavel significa “endereço de Variavel”
95
4) Definições de novos tipos de expressões e variáveis
3) Novos tokens %token DECLPTR /* – declaração de ponteiro */ %token LOCAPONT /* ‘#’ – local apontado por */ %token ENDER /* ‘&’ – endereço de variável */ 4) Definições de novos tipos de expressões e variáveis #define NAOVAR 0 #define INTEIRO 1 #define LOGICO 2 #define REAL 3 #define CARACTERE 4 #define PTRINT 5 #define PTRLOG 6 #define PTRREAL 7 #define PTRCARAC 8
96
5) Novo campo na tabela de símbolos:
O flag ehpont que sinaliza se uma variável é ou não um ponteiro 6) Tabela de compatibilidade para os operadores de ponteiros: Operador Operandos admitidos # Variável ponteiro & Qualquer variável
97
7) Declaração de variáveis ponteiros:
ElemDecl : DECLPTR ID { if (ProcuraSimb ($2) != NULL) DeclaracaoRepetida ($2); else { switch (tipocorrente) { case INTEIRO: tipoprt = PTRINT; case REAL: tipoprt = PTRREAL; case LOGICO: tipoprt = PTRLOG; case CARACTERE: tipoprt = PTRCARAC; } InsereSimb ($2, IDVAR, tipoptr); ;
98
8) Compatibilidade em expressões:
Fator : LOCAPONT Variavel { if ($2) if (!$2->ehpont) Esperado ("Variavel ponteiro"); else switch ($2->tvar) { case PTRINT: $$ = INTEIRO; break; case PTRLOG: $$ = LOGICO; break; case PTRREAL: $$ = REAL; break; case PTRCARAC: $$ = CARACTERE; break; } ;
99
Tipo admitido do lado direito
9) Compatibilidade em comandos de atribuição: Tabela de compatibilidade: Tipo do lado esquerdo Tipo admitido do lado direito Inteiro Inteiro ou Caractere Real Inteiro, Real ou Caractere Caractere Lógico Ponteiro Ponteiro de mesmo tipo
100
%type <tipoexpr> LadoEsquerdo
Teste de compatibilidade: nas produções: CmdAtrib : LadoEsquerdo := Expressao ; | LadoEsquerdo := & Variavel ; LadoEsquerdo: Variavel | # Variavel Atributo do não-terminal LadoEsquerdo: %type <tipoexpr> LadoEsquerdo A seguir a programação em Yacc
101
LadoEsquerdo : Variavel { if ($1) $$ = $1->tvar; }
| LOCAPONT Variavel { if ($2) if (!$2->ehpont) Esperado ("Variavel ponteiro"); else switch ($2->tvar) { case PTRINT: $$ = INTEIRO; break; case PTRLOG: $$ = LOGICO; break; case PTRREAL: $$ = REAL; break; case PTRCARAC: $$ = CARACTERE; break; } ; Cálculo do atributo de LadoEsquerdo
102
CmdAtrib : LadoEsquerdo ATRIB Expressao PVIRG
{ switch ($1) { case INTEIRO: case CARACTERE: if ($3 != INTEIRO && $3 != CARACTERE) Incompatibilidade ("Tipos incompativeis numa atribuicao"); break; case REAL: if ($3 != INTEIRO && $3 != CARACTERE && $3 != REAL) ("Tipos incompativeis numa atribuicao"); break; case LOGICO: if ($3 != LOGICO) default: if ($1 != $3) } ;
103
CmdAtrib : LadoEsquerdo ATRIB ENDER Variavel PVIRG
{ switch ($1) { case INTEIRO: case CARACTERE: case REAL: case LOGICO : Incompatibilidade ("Tipos incompativeis numa atribuicao"); break; default: if ($1 == PTRINT && $4 != INTEIRO || $1 == PTRREAL && $4 != REAL || $1 == PTRLOGIC && $4 != LOGICO || $1 == PTRCARAC && $4 != CARACTERE) ("Tipos incompativeis numa atribuicao"); } ;
104
6.3.6 – Comentários sobre o poder dos atributos
Como já foi visto, Yacc possibilita que os terminais e os não-terminais da gramática carreguem consigo atributos compostos Isso dá um poder muito grande aos seus programas Na redução de uma produção, os campos do atributo no não-terminal à esquerda podem ser calculados em função daqueles dos símbolos da direita
105
Esses atributos calculados são usados para o cálculo de outros atributos, em outras reduções
É um cálculo bottom-up dos atributos dos elementos da árvore sintática do programa analisado Como escolher quais campos terá o atributo de um não-terminal? Depende do papel desse não-terminal no processo de compilação
106
Por exemplo, um não-terminal envolvido na formação de expressões é usado:
Em testes de compatibilidade de tipos entre operadores e operandos Para determinar os operandos das quádruplas com operadores aritméticos, relacionais e lógicos, na geração do código intermediário Para guardar valores intermediários no cálculo de expressões
107
Portanto é útil que seu atributo tenha campos para guardar:
O tipo do operando que ele representa numa operação aritmética ou lógica (inteiro, real, caractere, lógico, etc.) O tipo do operando ou resultado (variável, constante, etc.) da quádrupla gerada para a tradução da operação aritmética ou lógica em que está envolvido Mais informações sobre os operandos das quádruplas (nome de variável, valor de constante, etc.); Valores intermediários usados no cálculo de uma expressão aritmética ou lógica, etc.
108
Outros não terminais podem ter campos dos mais variados tipos e complexidade:
Lista de rótulos de comandos case Lista de tipos dos argumentos de chamada de funções Número de argumentos, número de índices de elementos de variáveis indexadas, etc. Esses campos vão aparecendo no projeto do compilador, conforme sua necessidade: Ao implementar determinados testes semânticos Ao implementar a geração de quádruplas, etc.
109
Atributos podem ser dados às ações no meio das produções
Foi visto que em Yacc, ações no meio das produções geram produções vazias de não terminais fictícios, que passam a ter tais ações no final Com esses atributos de ações, consegue-se transmitir informações de uma ação para outra, numa mesma produção
110
6.3.7 – Testes relativos à sub-programação
a) Abrangência da abordagem Aqui serão vistos testes de compatibilidade entre: Argumentos de chamadas das funções e seus respectivos parâmetros Tipo das expressões retornadas por uma função e tipo declarado da própria função
111
Para ilustrar esta Seção 6. 3
Para ilustrar esta Seção adotar-se-á o modelo da Linguagem C, no tocante à passagem de parâmetros Então só será abordada a passagem de parâmetros por valor A passagem por referência será simulada pelo uso de ponteiros e pelo operador & que manipula o endereço de variáveis Para evitar ambiguidades, o operador AND será representado por “&&” Para simplificar, supõe-se que a linguagem não trabalhe com blocos
112
Os testes aqui estudados, relacionados com a compatibilidade entre os argumentos de chamada e os parâmetros de uma função, são os seguintes: O número de argumentos de chamada deve ser igual ao número de parâmetros Os tipos dos argumentos de chamada devem ser compatíveis com os tipos dos parâmetros
113
Tabela de compatibilidade adotada nesta abordagem:
** Se o parâmetro for um vetor, o argumento pode ser uma linha de uma matriz bi-dimensional, desde que seus elementos sejam de mesmo tipo e em mesmo número dos elementos do parâmetro
114
b) Preparo da tabela de símbolos
É útil cada identificador de função conter em sua célula na tabela de símbolos: Um campo com o número de seus parâmetros; seja nparam o nome desse campo Um campo com a lista de células de seus parâmetros; seja ListParam o nome dessa lista
115
Exemplo: Seja o trecho de programa: // Escopo global float x; . . .
int ff (int b, . . .) { int a, . . . } ##global IDGLOB tid ListVarDecl ListFunc escopo # # x IDVAR FALSO REAL tid param tvar escopo ff IDFUNC tid ListVarDecl List Param escopo INT 1+... tvar nparam # # a IDVAR FALSO INT tid param tvar escopo b IDVAR VERD INT tid param tvar escopo
116
Programação no não-terminal Prog:
InicTabSimb (); declparam = FALSO; } DeclGlobais Funcoes ; Indicando que parâmetros não estão sendo declarados
117
Programação no não-terminal Prog:
InicTabSimb (); declparam = FALSO; escopo = simb = InsereSimb ("global##", IDGLOB, NAOVAR, NULL); } DeclGlobais Funcoes ; ##global IDGLOB tid ListVarDecl ListFunc escopo # escopo simb InsereSimb aloca o nó-líder de ListVarDecl e ListFunc
118
Programação no não-terminal Prog:
InicTabSimb (); declparam = FALSO; escopo = simb = InsereSimb ("global##", IDGLOB, NAOVAR, NULL); pontvardecl = simb->listvardecl; pontfunc = simb->listfunc; } DeclGlobais Funcoes ; ##global IDGLOB tid ListVarDecl ListFunc escopo # escopo pontvardecl simb Prepara para inserir a primeira variável global e a primeira função nas listas do escopo global pontfunc
119
Programação no não-terminal Cabecalho:
Cabeçalho : Tipo ID { escopo = simb = InsereSimb ($2, IDFUNC, tipocorrente, escopo); } ABPAR {declparam = VERDADE;} Listparam FPAR ; ##global IDGLOB tid ListVarDecl ListFunc escopo # escopo pontvardecl pontfunc escopo simb # ff IDFUNC tid ListVarDecl List Param escopo INT tvar nparam simb pontfunc
120
Programação no não-terminal Cabecalho:
Cabeçalho : Tipo ID { escopo = simb = InsereSimb ($2, IDFUNC, tipocorrente, escopo); pontvardecl = simb->listvardecl; pontparam = simb->listparam; } ABPAR {declparam = VERDADE;} Listparam FPAR ; Prepara para inserir a primeira variável local e o primeiro parâmetro nas listas do escopo da função ##global IDGLOB tid ListVarDecl ListFunc escopo # pontvardecl pontfunc escopo ListVarDecl List Param tid tvar nparam simb ff IDFUNC INT escopo pontvardecl pontparam # #
121
Programação no não-terminal Cabecalho:
Cabeçalho : Tipo ID { escopo = simb = InsereSimb ($2, IDFUNC, tipocorrente, escopo); pontvardecl = simb->listvardecl; pontparam = simb->listparam; } ABPAR {declparam = VERDADE;} Listparam FPAR {declparam = FALSO;} ; Indicando que parâmetros começam a ser declarados Indicando que parâmetros não mais estão sendo declarados
122
Nova função InsereSimb:
simbolo InsereSimb (char *cadeia, int tid, int tvar, simbolo escopo) { int i; simbolo aux, s; /* Codigo comum a todos os identificadores */ i = hash (cadeia); aux = tabsimb[i]; s = tabsimb[i] = malloc (sizeof (celsimb)); s->cadeia = malloc ((strlen(cadeia)+1)* sizeof(char)); strcpy (s->cadeia, cadeia); s->prox = aux; s->tid = tid; s->tvar = tvar; s->escopo = escopo;
123
/* Codigo para parametros e variáveis globais e locais */
if (declparam) { s->inic = s->ref = s->param = VERDADE; } else { s->inic = s->ref = s->param = FALSO; O identificador inserido é um parâmetro Todo parâmetro é considerado referenciado e inicializado
124
/* Codigo para parametros e variáveis globais e locais */
if (declparam) { s->inic = s->ref = s->param = VERDADE; if (s->tid == IDVAR) InsereListSimb (s, &pontparam); s->escopo->nparam++; } else { s->inic = s->ref = s->param = FALSO; Insere o parâmetro na lista de parâmetros de seu escopo Acrescenta 1 ao número de parâmetros de seu escopo
125
/* Codigo para parametros e variáveis globais e locais */
if (declparam) { s->inic = s->ref = s->param = VERDADE; if (s->tid == IDVAR) InsereListSimb (s, &pontparam); s->escopo->nparam++; } else { s->inic = s->ref = s->param = FALSO; InsereListSimb (s, &pontvardecl); Insere uma variável na lista de variáveis globais ou locais de uma função
126
/* Codigo para identificador global ou nome de função */
if (tid == IDGLOB || tid == IDFUNC) { s->listvardecl = (elemlistsimb *) malloc (sizeof (elemlistsimb)); s->listvardecl->prox = NULL; } if (tid == IDGLOB) { s->listfunc = (elemlistsimb *) s->listfunc->prox = NULL; Insere o nó-líder da lista de variáveis globais ou locais de uma função Insere o nó-líder da lista de funções do escopo global
127
/* Codigo para nome de função e retorno de Inserir */
if (tid == IDFUNC) { s->listparam = (elemlistsimb *) malloc (sizeof (elemlistsimb)); s->listparam->prox = NULL; s->nparam = 0; InsereListSimb (s, &pontfunc); } return s; Insere o nó-líder da lista de parâmetros do escopo de uma função Inicializa com zero o número de parâmetros de uma função Insere o nome da função na lista de funções do escopo global
128
c) Teste de compatibilidade entre parâmetros e argumentos escalares
Quando os parâmetros e os argumentos envolvidos são escalares, os testes de compatibilidade são mais simples É necessário calcular o número de argumentos de chamada e formar uma lista com os tipos dos argumentos Esses, em seguida, podem ser comparados com o número de parâmetros e com a lista dos mesmos já organizada na tabela de símbolos
129
Exemplo: seja o seguinte código
int ff1 (int a, float b, logic c) { } int ff2 ( ) { a = ff1 (expr1, expr2, expr3); } ff1 IDFUNC 3 tid nparam ListParam # a IDVAR INTEIRO tid escopo tvar Na chamada de ff1: O número de expressões deve ser 3 Deve haver compatibilidade entre (expr1, a), (expr2, b) e (expr3, c) b IDVAR REAL tid escopo tvar c IDVAR LOGICO tid escopo tvar
130
Produções envolvidas:
CallFunc : ID ( Argumentos ) Argumentos : ε | ListExpr ListExpr : Expressao | ListExpr , Expressao É conveniente que os não-terminais ListExpr e Argumentos tenham como atributos: Número de argumentos (expressões) Lista de tipos dos argumentos (tipos das expressões) typedef struct infolistexpr infolistexpr; struct infolistexpr { pontexprtipo listtipo; int nargs; };
131
Acrescenta-se a seguinte declaração na %union:
infolistexpr infolexpr; E a seguinte declaração de atributo de não-terminais: %type <infolexpr> ListExpr Argumentos A seguir a programação nos três não-terminais envolvidos
132
Nos não-terminais ListExpr e Argumentos :
ListExpr : Expressao { $$.nargs = 1; $$.listtipo = InicListTipo ($1.tipo); } | ListExpr VIRG Expressao { $$.nargs = $1.nargs + 1; $$.listtipo = ConcatListTipo ($1.listtipo, InicListTipo ($3.tipo)); ; Argumentos : {$$.nargs = 0; $$.listtipo = NULL;} | ListExpr /* default: $$ = $1; */
133
No não-terminal CallFunc :
CallFunc : ID ABPAR { simb = ProcuraSimb ($1, escopo->escopo); if (! simb) NaoDeclarado ($1); else if (simb->tid != IDFUNC) TipoInadequado ($1); $<simb>$ = simb; } Argumentos FPAR { $$ = $<simb>3; if ($$ && $$->tid == IDFUNC) { if ($$->nparam != $4.nargs) Incompatibilidade ("Numero de argumentos diferente do numero de parametros"); ChecArgumentos ($4.listtipo, $$->listparam); } ; Atributo de CallFunc: Ponteiro para o nome da função na TabSimb Necessário: %type <simb> CallFunc Necessário para calcular o tipo de Fator na produção Fator : CallFunc A seguir a função ChecArgumentos
134
void ChecArgumentos (pontexprtipo Ltiparg, listsimb Lparam) {
pontexprtipo p; pontelemlistsimb q; p = Ltiparg->prox; q = Lparam->prox; ff1 IDFUNC 3 tid nparam ListParam ######## INTEIRO LOGICO Ltiparg # p Lparam q a IDVAR INTEIRO tid escopo tvar b IDVAR REAL tid escopo tvar c IDVAR LOGICO tid escopo tvar
135
while (p != NULL && q != NULL) {
switch (q->simb->tvar) { case INTEIRO: case CARACTERE: if (p->tipo != INTEIRO && p->tipo != CARACTERE) Incompatibilidade("...."); break; case REAL: if (p->tipo != INTEIRO && p->tipo != CARACTERE && p->tipo != REAL) Incompatibilidade("...."); break; case LOGICO: if (p->tipo != LOGICO) default: if (q->simb->tvar != p->tipo) } p = p->prox; q = q->prox;
136
d) Teste de compatibilidade entre parâmetros e argumentos do tipo variável indexada
Esse teste exige alterações na função ChecArgumentos, bem como nas ações de algumas produções da gramática Se um parâmetro for uma variável indexada, o argumento correspondente deve ser uma variável de mesmo tipo e dimensão Se esse parâmetro for um vetor, o argumento pode ser um vetor ou uma linha de uma matriz bi-dimensional de mesma dimensão
137
Expressões contendo um só elemento
Exemplo: seja o código int ff (float A[ , ], int B[ ]) { } void main ( ) { float X [... , ... , ...]; int Y [... , ... , ...]; . . . = ff ( X[ ... ] , Y[ ... , ... ] ) ; É necessário verificar se uma expressão tem um só elemento: Mais informações nos atributos de expressões Expressões contendo um só elemento
138
e) Teste de compatibilidade da expressão retornada por uma função
Tabela de compatibilidade:
139
A programação para esse teste muito se assemelha com aquela feita para comandos de atribuição
As produções envolvidas são: CmdReturn : RETURN | RETURN Expressão É necessário comparar o tipo da expressão com o tipo da função que define o escopo do comando return No caso da primeira produção, o tipo da função deve ser void
140
6.4 – Testes Semânticos em Análise Preditora
Seja o método apresentado na Seção do capítulo sobre análise sintática: Cada não-terminal tem seu procedimento (sub-programa) particular Nesse método, as ações para análise semântica e para geração de código intermediário são recheios no procedimento de cada não-terminal. Resta apresentar a implementação de atributos para não-terminais nesse método
141
Exemplo: seja a gramática para expressões:
A idéia é que os procedimentos se tornem funções e os atributos sejam os valores retornados por essas funções Exemplo: seja a gramática para expressões: Expressão Termo Eaux Eaux OPAD Termo Eaux | ε Termo Fator Taux Taux OPMULT Fator Taux| ε Fator CTE | ( Expressão ) | OPNEG Fator A seguir um analisador sintático preditor recursivo: 141
142
Programa principal: void main () { printf ("Digite a expressao:\n\n");
fflush (stdin); gets (expressao); printf ("\n"); ptexpr = 0; carac = NovoCarac (); NovoAtomo (); Expressao (); if (atom.tipo != ENDOFFILE) Esperado (ENDOFFILE); }
143
void Expressao () {Termo (); Eaux ();} void Eaux () {
if (atom.tipo == OPAD) { NovoAtomo (); Termo (); Eaux (); } void Termo () {Fator (); Taux ();} void Taux () { if (atom.tipo == OPMULT) { NovoAtomo (); Fator (); Taux (); Todas as funções são do tipo void
144
void Fator () { switch (atom.tipo) { case CTE: NovoAtomo (); break; case ABPAR: NovoAtomo (); Expressao (); if (atom.tipo != FPAR) Esperado (FPAR); NovoAtomo (); break; case OPNEG: NovoAtomo (); Fator (); break; default: NaoEsperado (atom.tipo); }
145
No Yacc, usa-se atributos para valores intermediários e finais
Este programa pode ser usado como esqueleto para implementar uma calculadora simples No Yacc, usa-se atributos para valores intermediários e finais Nesta análise preditora, cada função passará a retornar valores do tipo atribnonterm (equivalentes a atributos no Yacc) /* Estrutura dos atributos dos não-terminais */ typedef struct atribnonterm atribnonterm; struct atribnonterm { float valor; int oper; };
146
Programa principal: void main () { atribnonterm x;
printf ("Digite a expressao:\n\n"); fflush (stdin); gets (expressao); printf ("\n"); ptexpr = 0; carac = NovoCarac (); NovoAtomo (); x = Expressao (); if (atom.tipo != ENDOFFILE) Esperado (ENDOFFILE); else printf ("\nValor da expressao: %g",x.valor); } Se houver erros além deste, o valor estará errado
147
/* Funcao para Expressao */ atribnonterm Expressao () {
atribnonterm x, y; x = Termo (); y = Eaux (); if (y.oper == MAIS) x.valor += y.valor; else x.valor -= y.valor; return x; } As funções não são mais do tipo void
148
/* Funcao para Eaux */ atribnonterm Eaux () {
atribnonterm x, y; long oper; x.valor = 0; x.oper = MAIS; if (atom.tipo == OPAD) { oper = atom.atrib.atr; NovoAtomo (); x = Termo (); y = Eaux (); if (oper == MENOS) y.valor *= -1; if (y.oper == MAIS) x.valor += y.valor; else x.valor -= y.valor; x.oper = oper; } return x;
149
/* Funcao para Termo */ atribnonterm Termo () { atribnonterm x, y; x = Fator (); y = Taux (); if (y.oper == VEZES) x.valor *= y.valor; else x.valor /= y.valor; return x; }
150
/* Funcao para Taux */ atribnonterm Taux () { atribnonterm x, y; long oper; x.valor = 1; x.oper = VEZES; if (atom.tipo == OPMULT) { oper = atom.atrib.atr; NovoAtomo (); x = Fator (); y = Taux (); if (oper == DIV) y.valor = 1 / y.valor; if (y.oper == VEZES) x.valor *= y.valor; else x.valor /= y.valor; x.oper = oper; } return x;
151
/* Funcao para Fator */ atribnonterm Fator () { atribnonterm x; switch (atom.tipo) { case CTE: x.valor = atom.atrib.valor; NovoAtomo (); break; case ABPAR: NovoAtomo (); x = Expressao (); if (atom.tipo != FPAR) Esperado (FPAR); case OPNEG: NovoAtomo (); x = Fator (); x.valor *= -1; break; default: NaoEsperado (atom.tipo); } return x;
152
No exemplo visto, os atributos de todos os não-terminais são do mesmo tipo e tem dois campos: valor e oper. Em gramáticas mais complexas, os tipos podem variar de não-terminal para não-terminal.
153
6.5 – Formalização de Análise Semântica
6.5.1 – Esquemas de tradução O processo de análise semântica apresentado até aqui tem caráter um tanto informal; bem mais informal que os processos de análise sintática vistos. Por exemplo, as especificações semânticas das linguagens foram feitas quase que desassociadas das produções das gramáticas.
154
Para resolver os testes semânticos foram usadas inúmeras variáveis globais, tais como pilhas, flags, etc., que nada têm a ver com a gramática da linguagem analisada. Se o Yacc não usasse atributos associados aos símbolos nas reduções, seriam necessárias muitas outras pilhas e flags, para a análise semântica e outros recheios da análise sintática.
155
A literatura sobre compiladores apresenta um certo grau de formalização da análise semântica e desses outros recheios. O nome dado a essa formalização é esquema de tradução e isso engloba a utilização de atributos dos símbolos.
156
Esquema de tradução é uma generalização de gramática livre de contexto na qual:
Cada ocorrência de símbolo em uma produção tem associado um conjunto de atributos; Esses atributos são definidos e computados através de regras semânticas (ações) inseridas em determinadas posições das produções da gramática (no início, meio e fim).
157
Tal como já foi visto, esses atributos podem ser valores de constantes numéricas ou não, ponteiros para um símbolo da tabela de símbolos, listas de símbolos ou tipos ou constantes, etc.. Também como já foi visto, na árvore sintática de uma sentença ou programa, esses atributos ficam localizados em seus nós. Atributo não é propriedade de uma produção, mas sim de uma instância de produção no processo de análise
158
Árvore sintática com atributos para
Exemplo 6.11: Seja o seguinte esquema de tradução para expressões aritméticas: L E ‘\n’ {L.fict Imprimir (E.val)} E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Para ações sem atribuições, cria-se atributos fictícios para serem lados-esquerdos Árvore sintática com atributos para 25 * \n
159
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Uma ação só pode ser executada, quando seus operandos estiverem disponíveis No início, os únicos atributos disponíveis são os lexval’s
160
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Agora, as ações que podem ser executadas são as do tipo F.val num.lexval
161
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Agora, as ações que podem ser executadas são as do tipo T.val F.val
162
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Agora, a ação a ser executada é T.val T1.val * F.val
163
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Agora, a ação a ser executada é E.val T.val
164
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} Agora E.val E1.val + T.val
165
L E ‘\n’ {L.fict Imprimir (E.val)}
E E1 ‘+’ T {E.val E1.val + T.val} E T {E.val T.val} T T1 ‘*’ F {T.val T1.val * F.val} T F {T.val F.val} F ‘(’ E ‘)’ {F.val E.val} F num {F.val num.lexval} E finalmente L.fict Imprimir (E.val)
166
6.5.2 – Atributos sintetizados e hereditários
No exemplo anterior, os atributos em um nó da árvore sintática são calculados em função dos atributos dos filhos desse nó. Atributos com essa característica recebem o nome de atributos sintetizados. Atributos sintetizados são transmitidos pela árvore na direção bottom-up. É concebível também uma outra modalidade de atributos que são transmitidos pela árvore na direção top-down.
167
Por exemplo, pode ser útil, para um não terminal, informação sobre o escopo onde ele está sendo processado. Escopo é uma típica informação a ser transmitida na direção top-down. Atributos com essa característica recebem o nome de atributos hereditários. Numa árvore sintática, os atributos hereditários de um nó são calculados em função dos atributos dos pais e irmãos desse nó.
168
Tal variável nada tem a ver com as produções da gramática
Exemplo: Esquema de tradução com atributos hereditários (declaração de tipos de variáveis) Recorda-se que, em Yacc, usou-se a variável tipocorrente para armazenar o tipo de uma variável na TabSimb Tal variável nada tem a ver com as produções da gramática A seguir um esquema de tradução para esse armazenamento: 168
169
Árvore sintática para a sentença float id1, id2, id3:
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Árvore sintática para a sentença float id1, id2, id3: 169
170
O atributo her é hereditário
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Árvore sintática para a sentença float id1, id2, id3: O atributo her é hereditário Na produção D T L her é função de tipo do nó irmão Na produção L L1 , id L1.her é função de her do nó pai 170
171
O atributo her é hereditário
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Árvore sintática para a sentença float id1, id2, id3: O atributo her é hereditário O atributo entr de id é uma referência à TabSimb IntrodTipo coloca o valor de her na célula que guarda id na TabSimb fict é um atributo fictício para receber IntrodTipo, que não retorna valor 171
172
Árvore sintática para a sentença float id1, id2, id3:
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Árvore sintática para a sentença float id1, id2, id3: Atributos são associados aos símbolos da linguagem Cada nó da árvore pode possuir um ou mais atributos Cada atributo de um nó é calculado por um comando das ações do esquema de tradução 172
173
T int {T.tipo INTEIRO} T float {T.tipo REAL}
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Inicialmente: T.tipo REAL 173
174
T int {T.tipo INTEIRO} T float {T.tipo REAL}
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Em seguida: L.her T.tipo 174
175
T int {T.tipo INTEIRO} T float {T.tipo REAL}
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Depois: L.fict IntrodTipo (id.entr, L.her) L1.her L.her 175
176
T int {T.tipo INTEIRO} T float {T.tipo REAL}
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} Ainda depois: L.fict IntrodTipo (id.entr, L.her) L1.her L.her 176
177
T int {T.tipo INTEIRO} T float {T.tipo REAL}
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} E finalmente: L.fict IntrodTipo (id.entr, L.her) 177
178
T int {T.tipo INTEIRO} T float {T.tipo REAL}
D T L {L.her T.tipo} T int {T.tipo INTEIRO} T float {T.tipo REAL} L L1 , id {L1.her L.her; L.fict IntrodTipo (id.entr, L.her)} L id {L.fict IntrodTipo (id.entr, L.her)} O atributo her é calculado na direção top-down na árvore 178
179
S a b A c D A a b A c | ε D d d D | ε
Em Yacc, trabalha-se com atributos sintetizados; atributos de um nó da árvore sintática podem influir no cálculo dos atributos de seus ancestrais. Além disso, numa produção, os atributos dos símbolos dos dois lados podem ser calculados uns em função dos outros, indistintamente. Não é possível, em Yacc, que o atributo de um nó influa no cálculo dos atributos de seus descendentes na árvore, com a exceção daqueles de seus filhos. Exemplo 6.13: Programa em Yacc para a gramática S a b A c D A a b A c | ε D d d D | ε 179
180
%token dolar %token erro %% SS : S dolar {
%token a %token b %token c %token d %token dolar %token erro %% SS : S dolar { printf ("Fim da analise\n"); return 0;} ; S : a {$1 = 5;} b {$$ = 8;} A {$5 = $1 + 2; $3 = $4 + $5;} c D { $$ = $1 + $3 + $5 + 10; $8 = 10 * $$; printf ("$$ = %d; $8 = %d;\n", $$, $8); } A : | a b A c ; D : | d d D ; yylex () { char x; x = getchar (); while (x == ' ' || x == '\n' || x == '\t' || x == '\r') printf ("Caractere lido: %c\n", x); if (x == 'a') return a; if (x == 'b') return b; if (x == 'c') return c; if (x == 'd') return d; if (x == '$') return dolar; return erro; } Saída para abcdd$ : Caractere lido: a Caractere lido: b Caractere lido: c Caractere lido: d Caractere lido: $ $$ = 37; $8 = 370; Fim da analise 180
181
Exemplo : Trocando no exemplo anterior as ações da produção de S por:
Ainda em Yacc, numa mesma produção, não se pode referenciar à esquerda de um símbolo o seu atributo. Exemplo : Trocando no exemplo anterior as ações da produção de S por: S : a {$1 = 5;} b {$$ = 8;} A {$5 = $1 + 2; $8 = $4 + $5;} c D { $$ = $1 + $3 + $5 + 10; $8 = 10 * $$; printf ("$$ = %d; $8 = %d;\n", $$, $8); } ; Ou seja, referenciando $8, que é o atributo de D, à esquerda de D, a execução do Yacc acusará erro. 181
182
Numa análise preditora recursiva procedimental:
Atributos hereditários podem ser passados como parâmetros para os procedimentos dos não-terminais Conforme já foi visto, atributos sintetizados podem ser valores retornados desses procedimentos. 182
183
6.5.3 – Grafo de dependências dos atributos
O fato de atributos de alguns nós de uma árvore sintática serem calculados em função daqueles de outros nós concebe um grafo de dependências Exemplo 6.15: Grafo de dependências da árvore
184
O grafo deve ser acíclico, senão está errado
185
Ordenação topológica:
Sendo esse grafo acíclico, a execução das ações do esquema deve obedecer a uma ordenação topológica do grafo Ordenação topológica: tipo1 REAL her3 tipo1 fict3 IntrodTipo (entr3, her3) her2 her3 fict2 IntrodTipo (entr2, her2) her1 her2 fict1 IntrodTipo (entr1, her1)
186
Muitos esquemas de tradução dispensam a construção de árvores sintáticas
Mas o uso de atributos sintetizados e hereditários fica bem determinado se essas existirem. Assim, conceitualmente falando, dado um esquema de tradução e uma sentença a ser analisada, pode-se realizar os seguintes passos:
Apresentações semelhantes
© 2024 SlidePlayer.com.br Inc.
All rights reserved.