A apresentação está carregando. Por favor, espere

A apresentação está carregando. Por favor, espere

Capítulo VI Análise Semântica

Apresentações semelhantes


Apresentação em tema: "Capítulo VI Análise Semântica"— Transcrição da apresentação:

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:

187


Carregar ppt "Capítulo VI Análise Semântica"

Apresentações semelhantes


Anúncios Google