Capítulo III Diagramas de Transições CES-41 COMPILADORES Capítulo III Diagramas de Transições
Capítulo III – Diagramas de Transições 3.1 – Considerações iniciais 3.2 – Uma linguagem ilustrativa: Mini-Pascal 3.3 – Análise léxica por diagramas de transições 3.4 – Análise sintática por diagramas de transições 3.5 – Tabela de símbolos em diagramas de transições 3.6 – Testes semânticos em diagramas de transições 3.7 – Geração de código intermediário
3.1 – Considerações Iniciais Diagramas de transições: usados na construção do front-end de um compilador Forma didática de concepção de um front-end Há outros métodos mais eficientes, porém menos didáticos A seguir, análises léxica, sintática e semântica e geração de código intermediário por diagramas de transições Diagrama de transições léxicas é um autômato finito determinístico
Exemplo: Diagrama de transições para reconhecer constantes numéricas em Fortran d é um dígito genérico
Arcos de qualquer vértice para o vértice Erro não são mostrados, mas existem d é um dígito genérico
Estado não final Estado final d é um dígito genérico
São reconhecidas constantes como: 13, +13, -13, 13. , 1. 25,. 25, - São reconhecidas constantes como: 13, +13, -13, 13., 1.25, .25, -.25, -32.43, 13E-15, 13.E-15, -13.25E+72, .75E5 d é um dígito genérico
Para as transições sintáticas usa-se vários diagramas de transições; um para cada não-terminal Existem linguagens para as quais é difícil ou até impossível a utilização de diagramas de transições sintáticas Para ilustrar será utilizada a linguagem Mini-Pascal extraída de Pascal, apresentada logo a seguir
3.2 – Uma linguagem ilustrativa: Mini-Pascal 3.2.1 – Gramática do Mini-Pascal A gramática do Mini-Pascal não é recursiva à esquerda, mas apresenta a ambiguidade dos comandos condicionais Não tem subprogramas, nem variáveis indexadas e seu único comando repetitivo é o while Não-terminais estão em itálico Terminais são átomos obtidos do analisador léxico; são apresentados com LETRAS MAIÚSCULAS, ou com os caracteres que os identificam
Produções da gramática: Prog PROGRAM ID ; Decls CmdComp . Decls ε VAR ListDecl ListDecl DeclTip DeclTip ListDecl DeclTip ListId : Tip ; ListId ID ID , ListId Tip INTEGER BOOLEAN CmdComp BEGIN ListCmd END ListCmd Cmd Cmd ; ListCmd Cmd CmdIf CmdWhile CmdRead CmdWrite CmdAtrib CmdComp
Produções da gramática (continuação 1) : CmdIf IF Expr THEN Cmd | IF Expr THEN Cmd ELSE Cmd CmdWhile WHILE Expr DO Cmd CmdRead READ ( ListId ) CmdWrite WRITE ( ListW ) ListW ElemW ElemW , ListW ElemW Expr CADEIA CmdAtrib ID := Expr
Produções da gramática (continuação 2) : Expr ExprSimpl ExprSimpl OPREL ExprSimpl ExprSimpl Term Term OPAD ExprSimpl Term Fat Fat OPMULT Term Fat ID CTE ( Expr ) TRUE FALSE OPNEG Fat
3.2.2 – Especificações léxicas Cada átomo tem pelo menos um atributo chamado tipo Conforme o tipo do átomo, ele poderá ter outros atributos Se o átomo é uma palavra reservada, seu tipo é a própria palavra reservada e não há outros atributos Tabela de palavras reservadas:
letra ( letra | dígito )* 3.2.2 – Especificações léxicas Átomo identificador: seu tipo é ID e seu outro atributo é a cadeia de seus caracteres; sua sintaxe é letra ( letra | dígito )* Caracteres de identificadores que excederem o número de 16 são ignorados Constantes inteiras: seu tipo é CTE e seu outro atributo é o seu valor inteiro; esse valor tem de caber em 2 bytes Cadeias de caracteres: vêm entre apóstrofos (‘ ’); têm como tipo, CADEIA, e, como outro atributo, a cadeia de seus caracteres sem os apóstrofos
Tabela dos tipos e atributos dos operadores: OPAD : operador aditivo OPMULT : operador multiplicativo OPNEG : operador negador OPREL : operador relacional
Tabela dos tipos dos separadores (não possuem outros atributos): Os programas em Mini-Pascal não têm comentários Espaços em branco entre átomos são opcionais, com a exceção das palavras reservadas Essas não podem estar concatenadas com outras e com identificadores e constantes inteiras
3.3 – Análise Léxica por Diagramas de Transições 3.3.1 – Objetivos da análise léxica Os caracteres do programa são grupados em átomos Os átomos têm sua validade verificada Os átomos válidos são classificados e recebem seus atributos
Tabela de átomos e seus atributos Exemplo: Programa para o cálculo do fatorial de um numero lido: PROGRAM fatorial; VAR n, fat, i: INTEGER; BEGIN READ (n); fat := 1; i := 1; WHILE i <= n DO BEGIN fat := fat * i; i := i + 1 END; WRITE (‘O fatorial de’, n, ‘ eh ’, fat) END. Tabela de átomos e seus atributos
3.3.2 – Diagrama de transições A estrutura de um analisador léxico pode ser montada sobre um diagrama de transições de estados O diagrama é uma só entidade, mas, por razões didáticas, está apresentado em várias figuras Uma figura para cada transição que parte do estado inicial Supõe-se que, no estado inicial, o analisador já tenha em mãos um caractere
a) Uma letra é o primeiro caractere Em cada transição: Caractere(s) responsável(eis) Ações antes da transição
Forma Cadeia: introduz caractere numa cadeia Pega Caractere: lê novo caractere do programa; retorna ‘\0’ para end-of-file Class Cadeia: classifica cadeia formada Forma Átomo: forma novo átomo com o tipo obtido da classificação; se for ID, o atributo será a cadeia formada Possíveis átomos: Palavra reservada OPMULT - AND OPAD - OR OPNEG - NOT ID - cadeia
b) Um dígito é o primeiro caractere Forma número: obtém o número inteiro correspondente à cadeia formada Forma Átomo: forma novo átomo com o número formado e com o tipo CTE Átomo formado: CTE - número inteiro
c) Um apóstrofo é o primeiro caractere Se o fecha-apóstrofo for esquecido, o resto do programa será guardado em cadeia Átomo formado: CADEIA - cadeia
d) Um dos caracteres + - * / ~ = é o primeiro Class Caractere: classifica caractere lido Possíveis átomos: OPMULT - VEZES OPMULT - DIV OPAD - MAIS OPAD - MENOS OPNEG - NEG OPREL - IGUAL
e) O caractere < é o primeiro Possíveis átomos: OPREL - MENIG OPREL - DIFER OPREL - MENOR
f) O caractere > é o primeiro Possíveis átomos: OPREL - MAIG OPREL - MAIOR
g) O caractere : é o primeiro Possíveis átomos: ATRIB DPONTS
h) Um dos caracteres ; . , ( ) é o primeiro Possíveis átomos: PVIRG PONTO VIRG ABPAR FPAR
i) Um dos caracteres ‘\0’, ‘ ’, ‘\n’, ‘\t’, ‘\r’, ou qualquer outro é o primeiro Átomo de tipo FINAL: artificial Possíveis átomos: FINAL INVAL
3.3.3 – Implementação de um diagrama de transições léxicas Primeiramente, será apresentada uma função main para gerenciar a classificação de todos os átomos de um programa em Mini-Pascal Essa função tem a simples finalidade de testar o analisador léxico Ela não será usada quando esse analisador estiver integrado ao analisador sintático
nome nomearq; FILE *program, *result; void main () { printf ("A N A L I S E L E X I C A\n\n"); printf ("Nome do arquivo: "); fflush (stdin); gets (nomearq); program = fopen (nomearq, "r"); result = fopen ("atomosmp", "w"); carac = NovoCarac (); while (carac) { NovoAtomo (); ImprimeAtomo (); } printf ("\nAnalise do arquivo '%s' encerrada", nomearq); printf ("\n\nVer atomos no arquivo 'atomosmp'"); getch (); typedef struct atomo atomo; struct atomo { int tipo; atribatomo atrib; }; typedef union atribatomo atribatomo; union atribatomo { char *cadeia; long valor; int atr; char carac; }; Variáveis globais: nome nomearq; FILE *program, *result; char carac; atomo atom; char *cadeia; NovoAtomo: coloca em atom o tipo e o atributo do próximo átomo do programa; é o centro do analisador léxico NovoCarac: retorna o próximo caractere lido do programa; retorna ‘\0’ caso seja encontrado end-of-file
A função NovoAtomo: Coloca na estrutura atom o tipo e o atributo do próximo átomo encontrado Implementa o caminhamento pelo diagrama de transições léxicas A seguir o esquema geral de NovoAtomo
if (atom.tipo == ID || atom.tipo == CADEIA) free (atom.atrib.cadeia); void NovoAtomo () { int estado = 1; if (atom.tipo == ID || atom.tipo == CADEIA) free (atom.atrib.cadeia); cadeia = malloc(MAXCADEIA*sizeof(char)); *cadeia = 0; while (estado != 3) switch (estado) { case 1: - - - - - - - - - - ; break; case 2: - - - - - - - - - - ; break; case 4: - - - - - - - - - - ; break; case 5: - - - - - - - - - - ; break; case 6: - - - - - - - - - - ; break; case 7: - - - - - - - - - - ; break; case 8: - - - - - - - - - - ; break; } free (cadeia); Preparação para uma eventual formação de cadeia Caso o átomo anterior tenha como atributo uma cadeia, ela deve ser desalocada Aqui ocorrem as transições de estados e o preparo do átomo Depois de formado o átomo, a cadeia é desalocada
Transições a partir do Estado 1
case 1: switch (carac) { case '\'': - - - - -; estado = 5; break; case '+': case '-': case '*': case '/': case '~': case '=': case ';': case '.': case ',': case '(': case ')': - - - - -; estado = 3; break; case '<': - - - - -; estado = 6; break; case '>': - - - - -; estado = 7; break; case ':': - - - - -; estado = 8; break; case '\0': - - - - -; estado = 3; break; default: if (isalpha (carac)) {- - - - -; estado = 2;} else if (isdigit (carac)) {- - - - -; estado = 4;} else if ((isspace(carac) || iscntrl(carac)) && (carac != 0)) { - - - - -; estado = 1; } else {- - - - -; estado = 3; } break;
case 1: switch (carac) { default: if (isalpha (carac)) { FormaCadeia (); carac = NovoCarac(); estado = 2;} else if (isdigit (carac)) { estado = 4;}
case 1: switch (carac) { case '\'': carac = NovoCarac( ); estado = 5; break;
case 1: switch (carac) { case '+': case '-': case '*': case '/': case '~': case '=': case ';': case '.': case ',': case '(': case ')': atom = Classifica (); carac = NovoCarac(); estado = 3; break;
case 1: switch (carac) { case '<': carac = NovoCarac(); estado = 6; break; case '>': carac = NovoCarac(); estado = 7; break; case ':': carac = NovoCarac(); estado = 8; break;
case 1: switch (carac) { case '\0': atom.tipo = FINAL; estado = 3; break; default: if ----- else if ((isspace(carac) || iscntrl(carac)) && (carac != 0)) { carac = NovoCarac(); estado = 1;} else {atom.tipo = INVAL; atom.atrib.carac = carac; carac = NovoCarac(); estado = 3; }
case 2: if (isalnum (carac)) { FormaCadeia (); carac = NovoCarac(); estado = 2;} else { atom = ClassificaCadeia (); estado = 3;} break; case 4: if (isdigit (carac)) { FormaCadeia (); carac = NovoCarac();estado = 4;} else {atom = FormaNumero (); estado = 3;}
case 6: if (carac == '=') { atom.tipo = OPREL; atom.atrib.atr = MENIG; carac = NovoCarac();} else if (carac == '>'){ atom.tipo = OPREL; atom.atrib.atr = DIFER; else { atom.tipo = OPREL; atom.atrib.atr = MENOR; } estado = 3; break; Outros estados ficam como exercícios
void FormaCadeia (void): Funções auxiliares: void FormaCadeia (void): Armazena o novo caractere lido no final da variável cadeia É chamada para formar a cadeia de um identificador, palavra reservada, número ou constante cadeia de caracteres, ou um dos operadores and, or ou not atomo ClassificaCadeia (void): Classifica uma cadeia de caracteres Possíveis classes: Identificador palavra reservada operadores and, or e not; Retorna o átomo formado (tipo e atributo)
atomo FormaNumero (void): Funções auxiliares: atomo FormaNumero (void): Converte uma cadeia de dígitos decimais em seu valor numérico Retorna o átomo formado (tipo e atributo) atomo Classifica (void): Classifica átomos formados por apenas um caractere, exceto os inválidos Retorna o átomo formado (tipo e atributo se for o caso) int PalavraReserv (void): Verifica se uma cadeia é uma palavra reservada Retorna seu tipo, em caso positivo
Definição de constantes simbólicas para as palavras reservadas, para os outros tipos de átomos e para os atributos dos operadores (#defines):
3.4 – Análise Sintática por Diagramas de Transições 3.4.1 – Objetivos da análise sintática Verificar a estrutura sintática de um programa Servir de esqueleto para: Construção da tabela de símbolos Análise semântica Geração do código intermediário Exemplo: árvore sintática do programa do fatorial (várias figuras)
3.4.2 – Diagramas de transições Análise sintática por diagramas de transições é um método top-down preditor: Top-down: Partindo do símbolo inicial, através de derivações diretas, vai simulando a construção do programa analisado Preditor: Um só átomo é suficiente para decidir qual produção usar para fazer uma derivação direta
Características de um método preditor: Os átomos são analisados um por um, do início para o final do programa Para decidir qual produção usar numa derivação direta, não é necessário checar um átomo já analisado Não é necessário olhar para algum átomo que vai aparecer mais adiante A gramática não pode ser recursiva à esquerda A gramática deve estar fatorada à esquerda (assunto a ser estudado no capítulo sobre análise sintática)
A gramática do Mini-Pascal admite análise sintática preditora, tal como o método dos diagramas de transições Muitas gramáticas não admitem métodos preditores Requerem a análise de átomos futuros e/ou passados
Percorrer diagrama de X Usa-se um diagrama para cada não-terminal Nas transições, usa-se terminais ou não-terminais (na análise léxica: caracteres) Exemplo para não-terminais: sejam as produções: Y → . . . . . . X . . . . . X → . . . . . . X Percorrer diagrama de X Diagrama de Y Diagrama de X
A seguir, diagramas de transições sintáticas de cada não-terminal da gramática do Mini-Pascal Incluídas transições para tratamento de erros sintáticos Tal tratamento é bem simples, só a título de ilustração
a) Prog PROGRAM ID PVIRG Decls CmdComp PONTO FINAL
ExecTransic (X): Percorre o diagrama de X, antes de mudar de estado Estados 9 e 10: Tratamento de erro Incondicional: Mudança incondicional de estado
b) Decls ε VAR ListDecl
ListDecl ( DeclTipo )+ c) ListDecl DeclTipo DeclTipo ListDecl Para facilitar a construção do diagrama, pode-se escrever suas produções de outra maneira: ListDecl ( DeclTipo )+
d) DeclTipo ListId DPONTS Tip PVIRG
e) ListId ID ID VIRG ListId Pode-se escrever suas produções de outra maneira: ListId ID ( VIRG ID )*
f) Tip INTEGER BOOLEAN
g) CmdComp BEGIN ListCmd END
ListCmd Cmd ( PVIRG Cmd )* h) ListCmd Cmd Cmd PVIRG ListCmd Pode-se escrever suas produções de outra maneira: ListCmd Cmd ( PVIRG Cmd )*
i) Cmd CmdIf CmdWhile CmdRead CmdWrite | CmdAtrib CmdComp
IF Expr THEN IF Expr THEN Cmd ELSE Cmd j) CmdIf IF Expr THEN Cmd | IF Expr THEN Cmd ELSE Cmd Ambiguidade: a sub-forma sentencial IF Expr THEN IF Expr THEN Cmd ELSE Cmd tem duas árvores sintáticas Regra de solução: o ELSE corresponde ao último THEN encontrado O diagrama de transições resolve o problema
j) CmdIf IF Expr THEN Cmd | IF Expr THEN Cmd ELSE Cmd
k) CmdWhile WHILE Expr DO Cmd
l) CmdRead READ ABPAR ListId FPAR
m) CmdWrite WRITE ABPAR ListW FPAR
ListW ElemW ( VIRG ElemW )* n) ListW ElemW ElemW VIRG ListW Pode-se escrever suas produções de outra maneira: ListW ElemW ( VIRG ElemW )*
o) ElemW CADEIA Expr
p) CmdAtrib ID ATRIB Expr
Expr ExprSimpl ( OPREL ExprSimpl ) ? q) Expr ExprSimpl ExprSimpl OPREL ExprSimpl Pode-se escrever suas produções de outra maneira: Expr ExprSimpl ( OPREL ExprSimpl ) ?
ExprSimpl Term ( OPAD Term )* r) ExprSimpl Term Term OPAD ExprSimpl Pode-se escrever suas produções de outra maneira: ExprSimpl Term ( OPAD Term )*
Term Fat ( OPMULT Fat )* s) Term Fat Fat OPMULT Term Pode-se escrever suas produções de outra maneira: Term Fat ( OPMULT Fat )*
t) Fat ID CTE TRUE FALSE ABPAR Expr FPAR OPNEG Fat
3.4.3 – Implementação dos diagramas de transições sintáticas Primeiramente, uma função main para acionar o analisador sintático
Variáveis globais: logic erro nome nomearq; FILE *program, *result; void main () { printf ("A N A L I S E S I N T A T I C A\n\n"); printf ("Nome do arquivo: "); fflush (stdin); gets (nomearq); program = fopen (nomearq, "r"); result = fopen ("atomosmp", "w"); erro = FALSE; carac = NovoCarac (); NovoAtomo (); ExecProg (); printf ("\nAnalise do arquivo '%s' encerrada", nomearq); if (erro) printf ("\n\nPrograma com erros!!!"); printf ("\n\nVer analise no arquivo 'atomosmp'"); getch (); } Variáveis globais: logic erro nome nomearq; FILE *program, *result; char carac; atomo atom; char *cadeia; ExecProg: implementa o diagrama de transições do não-terminal Prog ; é o centro do analisador sintático
a)A função ExecProg:
void ExecProg () { int estado = 1; while (estado != 8) switch (estado) { case 1: if (atom.tipo == PROGRAM) {NovoAtomo (); estado = 2;} else {Esperado ("PROGRAM"); estado = 9;} break; case 2: if (atom.tipo == ID) {NovoAtomo (); estado = 3;} else {Esperado ("IDENTIFICADOR"); estado = 9;} case 3: if (atom.tipo == PVIRG) {NovoAtomo (); estado = 4;} else {Esperado ("PONTO E VIRGULA"); estado = 9; }
case 4: ExecDecls (); estado = 5; break; case 5: ExecCmdComp (); estado = 6; break; case 6: if (atom.tipo == PONTO) {NovoAtomo (); estado = 7;} else {Esperado ("PONTO"); estado = 10;} break; case 7: if (atom.tipo == FINAL) estado = 8; else {Esperado ("END OF FILE"); estado = 10;} case 9: if (atom.tipo == PVIRG) {NovoAtomo (); estado = 4;} else if (atom.tipo == FINAL) estado = 8; else {NovoAtomo (); estado = 9;} case 10: else {NovoAtomo (); estado = 10;} }
b) A função ExecDecls: void ExecDecls () { int estado = 11; while (estado != 13) switch (estado) { case 11: if (atom.tipo == VAR) {NovoAtomo (); estado = 12;} else estado = 13; break; case 12: ExecListDecl (); estado = 13; }
c) A função ExecListDecl: void ExecListDecl () { int estado = 14; while (estado != 16) switch (estado) { case 14: ExecDeclTipo (); estado = 15; break; case 15: if (atom.tipo == ID) estado = 14; else estado = 16; }
d) A função DeclTipo:
Funções para outros não-terminais ficam como exercícios void ExecDeclTipo () { int estado = 17; while (estado != 21) switch (estado) { case 17: ExecListId (); estado = 18; break; case 18: if (atom.tipo == DPONTS) {NovoAtomo (); estado = 19;} else {Esperado ("DOIS PONTOS"); estado = 22;} break; case 19: ExecTip (); estado = 20; break; case 20: if (atom.tipo == PVIRG) {NovoAtomo (); estado = 21;} else {Esperado ("PONTO E VIRGULA"); estado = 22;} break; case 22: if (atom.tipo == PVIRG) else if (atom.tipo == FINAL) estado = 21; else {NovoAtomo (); estado = 22;} break; } Funções para outros não-terminais ficam como exercícios
3.5 – Tabela de Símbolos em Diagramas de Transições 3.5.1 – Objetivos da tabela de símbolos Agregar informações sobre todos os identificadores do programa Disponibilizar essas informações para: Análise semântica Geração do código intermediário
Para o Mini-Pascal, considerando só a fase de análise, as informações são as seguintes: Cadeia de caracteres do identificador Tipo do identificador: Nome de programa Nome de variável Nome de função (para o Pascal padrão) Nome de procedimento (para o Pascal padrão)
Se for variável: Tipo da variável (inteira, booleana) Se é indexada (para o Pascal padrão) Se tem inicialização Se é referenciada Para a fase de síntese, outras informações: Endereço de memória Número de bytes ocupados, etc.
3.5.2 – Estrutura de dados para a tabela de símbolos Pode-se usar estrutura de 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’)
Exemplo: Tabela de símbolos (hashing) do programa do fatorial (NCLASSHASH = 23)
Neste exemplo, as classes têm somente 0 e 1 símbolo; Em outros, as listas das classes podem ser maiores.
Declarações para a tabela de símbolos: typedef struct celsimb celsimb; typedef celsimb *simbolo; struct celsimb { char *cadeia; int tid, tvar; logic inic, ref; simbolo prox; }; Variáveis globais: simbolo simb; simbolo tabsimb[NCLASSHASH];
3.5.3 – Implementação da tabela de símbolos em diagramas de transições A geração da tabela de símbolos é feita nas ações dos diagramas de transições sintáticas O símbolo com o nome do programa é inserido na produção do não-terminal Prog
Ampliado a seguir:
InsereSimb (atom.atrib.cadeia, IDPROG); Insere na tabela de símbolos o atributo cadeia do átomo recém-formado, como sendo do tipo identificador de programa Retorna um ponteiro para a célula inserida na tabela de símbolos Sua programação consiste em inserir numa tabela hashing
Os nomes de variáveis são inseridos nas transições do não terminal ListId
O diagrama de ListId é chamado: No diagrama de DeclTip, na região das declarações No diagrama de CmdRead, na região dos comandos Diagrama de CmdRead Diagrama de DeclTip
A inserção deve ocorrer na região das declarações e não na dos comandos aqui sim aqui não Diagrama de CmdRead Diagrama de DeclTip
Pode-se usar uma variável-flag de nome declarando (global): Na região de declarações: declarando = VERDADE; Nas outras regiões: declarando = FALSO; Em ListId , só se deve inserir quando declarando == VERDADE No início da função ExecProg (início da análise sintática): void ExecProg () { int estado = 1; while (estado != 8) switch (estado) { case 1: - - - - - - - - - - - - -
No diagrama de Decls : No início, declarando = VERDADE; No final, declarando = FALSO;
No diagrama de ListId , tratamento distinto para declarando == VERDADE declarando == FALSO
ProcuraSimb (atom . atrib . cadeia): Procura na tabela de símbolos a célula que guarda o atributo cadeia do átomo recém-formado
ProcuraSimb (atom . atrib . cadeia): Quando encontra, retorna um ponteiro para a célula procurada; quando não, retorna NULL Sua programação consiste em procurar numa tabela hashing
ProcuraSimb (atom . atrib . cadeia): Nas declarações, em caso positivo, é uma dupla declaração No comando READ, em caso negativo, é identificador usado mas não declarado
Nas declarações, o tipo da variável só aparece mais adiante, fora do diagrama de ListId Podem ser várias variáveis inseridas sem o tipo
Solução: colocar os símbolos de uma declaração numa lista linear de símbolos (lista de ponteiros para células - global) Mais adiante, quando aparecer o tipo na declaração, percorre-se essa lista, adicionando-o em cada célula
Exemplo: i, fat, n: INTEGER; listsimb: variável global
Para adicionar, nas células das variáveis declaradas, a informação sobre o tipo: No diagrama de DeclTip : A lista global de símbolos é anulada e inicializada vazia, antes da chamada do diagrama de ListId Depois da chamada do diagrama de Tip , adiciona-se o tipo às referidas células, usando o valor de outra variável global: tipocorrente
Para adicionar, nas células das variáveis declaradas, a informação sobre o tipo: E no diagrama de Tip :
AdicTipoVar adiciona, em cada símbolo da lista linear, a informação contida na variável global tipocorrente i, fat, n: INTEGER;
No comando READ, a célula com o nome da variável é marcada como inicializada e referenciada
No diagrama de CmdAtrib: Se encontrado na tabela de símbolos, o identificador do lado esquerdo deve ser marcado como inicializado e como referenciado Se não encontrado, é identificador usado mas não declarado
No diagrama de Fat : Se encontrado na tabela de símbolos, o identificador deve ser marcado como referenciado Se não encontrado, é identificador usado mas não declarado
3.6 – Testes Semânticos em Diagramas de Transições 3.6.1 – Especificações semânticas do Mini-Pascal Qualquer identificador de variável usado nos comandos do programa deve estar declarado Nenhum identificador pode ser declarado mais de uma vez no programa Toda variável deve ser inicializada e referenciada no programa O nome do programa não pode ser usado como variável
Especificações relacionadas com expressões: Num comando de atribuição, a expressão e a variável que recebe seu valor devem ser do mesmo tipo Os operadores +, -, ~, *, / e os relacionais <, <=, >, >= só admitem operandos inteiros Os operandos dos operadores = e <> devem ser de mesmo tipo Os operadores AND, OR e NOT só admitem operandos booleanos As expressões nos cabeçalhos dos comandos IF e WHILE devem ser booleanas
3.6.2 – Detecção de identificadores de variáveis não-declaradas Ao ser usado num comando, um identificador deve estar na tabela de símbolos O tipo do identificador deve ser IDVAR Um identificador é usado em: Comandos READ Lado esquerdo de comandos de atribuição Fatores de expressões
a) Detecção em Comandos READ: no diagrama do ListId
b) Detecção no lado esquerdo de um comando de atribuição:
c) Detecção no fator de uma expressão :
3.6.3 – Detecção de dupla declaração de identificadores No diagrama de ListId , ao ser declarado, um identificador não deve estar na tabela de símbolos
3.6.4 – Detecção de identificadores não referenciados e não inicializados Percorrer todas as classes de TabSimb Visitar célula por célula em cada classe Reportar aquelas marcadas como não referenciadas e/ou não inicializadas TabSimb
3.6.5 – Determinação do tipo das expressões Os testes semânticos relacionados com expressões requerem a determinação do tipo de expressões e sub-expressões: Os operadores +, -, ~, *, / e os relacionais <, <=, >, >= só admitem operandos inteiros Os operandos dos operadores =, <> devem ser de mesmo tipo Os operadores AND, OR e NOT só admitem operandos booleanos Num comando de atribuição, a expressão e a variável que recebe seu valor devem ser do mesmo tipo As expressões nos cabeçalhos dos comandos IF e WHILE devem ser booleanas
Sejam as seguintes produções contendo operadores do Mini-Pascal: Expr ExprSimpl ( OPREL ExprSimpl )? ExprSimpl Term ( OPAD Term )* Term Fat ( OPMULT Fat )* Fat OPNEG Fat Cada OPREL é cercado por duas ExprSimpl’s Cada OPAD é cercado por dois Term’s Cada OPMULT é cercado por dois Fat’s Cada OPNEG é seguido por um Fat Esses não-terminais representam os operandos de tais operadores
Expr ExprSimpl ( OPREL ExprSimpl )? ExprSimpl Term ( OPAD Term )* Term Fat ( OPMULT Fat )* Fat OPNEG Fat Os tipos dos operandos devem obedecer às especificações de compatibilidade com tais operadores Idéia: a execução dos diagramas de Expr, ExprSimpl, Term e Fat pode retornar o tipo da sub-expressão que representam
ret: variável de retorno Idéia: a execução dos diagramas de Expr, ExprSimpl, Term e Fat pode retornar o tipo da sub-expressão que representam int ExecExpr () { int ret; - - - - - - - return ret; } int ExecExprSimpl () { int ret; - - - - - - - return ret; } int ExecTerm () { int ret; - - - - - - - return ret; } int ExecFat () { int ret; - - - - - - - return ret; } ret: variável de retorno Deve guardar o tipo do não-terminal do diagrama executado O valor de ret deve ser calculado durante a execução do diagrama
Diagrama de Fat (função ExecFat): Seja cada um dos caminhos que levam, sem erros, ao estado 81
Na produção Fat ID: O tipo de Fat é o tipo da variável representada por ID
Nas produções Fat CTE | TRUE | FALSE O tipos de Fat são respectivamente inteiro, booleano e booleano: FALSE
Na produção Fat ( Expr ): O tipo de Fat é o tipo de Expr a ser apresentado adiante
Produção Fat OPNEG Fat : O tipo de Fat do lado esquerdo deve ser o tipo admitido pelo atributo de OPNEG O atributo de OPNEG pode ser: NOT, que só admite operando booleano NEG, que só admite operando inteiro É necessário um teste de compatibilidade entre o atributo de OPNEG e o tipo de Fat do lado direito
Produção Fat OPNEG Fat : Da análise sintática oper: variável local inteira, para guardar o atributo de um operador Em caso de erro, retorna-se o tipo admitido pelo atributo de OPNEG
Diagrama de Term: Term Fat ( OPMULT Fat )* Ao transitar de 75 para 76 pela primeira vez, não há teste de compatibilidade Ao voltar de 76 para 75, deve haver teste entre o OPMULT e o Fat anterior Ao transitar de 75 para 76 depois da primeira vez, deve haver teste entre o Fat e o OPMULT anterior Função ExecTerm
oper = 0, no início de ExecTerm Diagrama de Term: Term Fat ( OPMULT Fat )* Para não haver teste de compatibilidade, ao passar pela primeira vez: oper = 0, no início de ExecTerm
Diagrama de Term: Term Fat ( OPMULT Fat )* O valor de ret veio de Fat na última transição de 75 para 76
ExprSimpl Term ( OPAD Term )* Diagrama de ExprSimpl : ExprSimpl Term ( OPAD Term )* Estratégia análoga ao diagrama de Term
ExprSimpl Term ( OPAD Term )* Diagrama de ExprSimpl : ExprSimpl Term ( OPAD Term )* oper = 0, no início de ExecExprSimpl
ExprSimpl Term ( OPAD Term )* Diagrama de ExprSimpl : ExprSimpl Term ( OPAD Term )*
Expr ExprSimpl ( OPREL ExprSimpl )? Diagrama de Expr: Expr ExprSimpl ( OPREL ExprSimpl )? Ao transitar de 68 para 69 não há teste de compatibilidade Ao transitar de 69 para 70, deve haver teste entre o OPREL e o ExprSimpl anterior Ao transitar de 70 para 71, deve haver teste entre o novo ExprSimpl e o OPREL anterior
Expr ExprSimpl ( OPREL ExprSimpl )? Diagrama de Expr: Expr ExprSimpl ( OPREL ExprSimpl )?
Expr ExprSimpl ( OPREL ExprSimpl )? Diagrama de Expr: Expr ExprSimpl ( OPREL ExprSimpl )?
Expr ExprSimpl ( OPREL ExprSimpl )? Diagrama de Expr: Expr ExprSimpl ( OPREL ExprSimpl )? O tipo de uma expressão relacional é booleano
CmdAtrib ID ATRIB Expr Diagrama de CmdAtrib: CmdAtrib ID ATRIB Expr O tipo de Expr deve ser o mesmo da variável correspondente ao ID
CmdAtrib ID ATRIB Expr Diagrama de CmdAtrib: CmdAtrib ID ATRIB Expr tvar: variável local destinada a guardar o tipo da variável
CmdAtrib ID ATRIB Expr Diagrama de CmdAtrib: CmdAtrib ID ATRIB Expr texpr: variável local destinada a guardar o tipo da expressão
CmdIf IF Expr THEN Cmd Diagrama de CmdIf: | IF Expr THEN Cmd ELSE Cmd O tipo de Expr deve booleano
CmdIf IF Expr THEN Cmd Diagrama de CmdIf: | IF Expr THEN Cmd ELSE Cmd
CmdWhile WHILE Expr DO Cmd Diagrama de CmdWhile: CmdWhile WHILE Expr DO Cmd O tipo de Expr deve booleano
CmdWhile WHILE Expr DO Cmd Diagrama de CmdWhile: CmdWhile WHILE Expr DO Cmd
3.7 – Geração de Código Intermediário Supor que serão utilizadas quádruplas A geração das quádruplas deve estar contida nas ações dos diagramas de transições sintáticas Exemplo: Código intermediário não otimizado para o programa fatorial:
openmod: aloca memória para as variáveis locais e parâmetros de um módulo A estrutura de dados para essas quádruplas será vista em capítulo específico. O código intermediário deve ser otimizado e traduzido para Assembly