Linguagens de Programação – Capítulo 3 Descrição da Sintaxe Sintaxe – Forma das Semântica – Significado das Expressões Instruções Unidades Expressões Instruções Unidades Exemplo: Comando if do C Sintaxe: if (<expressão>) <instrução> Semântica: “Se o valor de expressão for verdadeiro...” Sintaxe: mais fácil de descrever que a semântica. Existe uma notação concisa e universalmente aceita para a descrição da sintaxe, mas não ainda para a semântica Página 1 Linguagens de Programação – Capítulo 3 Lexemas – Unidades sintáticas Descrição dada por uma especificação léxica, em nível mais baixo que a descrição sintática Exemplo: Palavras reservadas, identificadores, literais, operadores... Tokens Categorias de lexemas Às vezes um token tem somente um lexema possível, às vezes “infinitos” Exemplo: index = 2*count + 17 “index” e “count” são lexemas que pertencem à categoria “identificador” Página 2 Linguagens de Programação – Capítulo 3 Formas de definir as linguagens, através de: Reconhecedores de Linguagens Dispositivos para verificar se certa sentença está na linguagem Geradores de Linguagens Dispositivos para gerar sentenças da linguagem BNF – Backus Naur Form Notação mais usada para descrever formalmente as linguagens de programação “Metalinguagem ” ( linguagem usada para descrever linguagens) para linguagens de programação Gramática é o conjunto de regras que produz a linguagem Página 3 Linguagens de Programação – Capítulo 3 BNF Exemplo: comando “if ” do Pascal <if_stmt> <if_stmt> if <expr_lógica> then <stmt> if <expr_lógica> then <stmt> else<stmt> ou <if_stmt> if <expr_lógica> then <stmt> | if <expr_lógica> then <stmt> else<stmt> Exemplo: listas e recursividade <lista_ident> <ident> <seq> <letra> <dígito> <ident> | <ident> , <lista_ident> <letra> <seq> | <letra> <letra> <seq> | <dígito><seq> | <letra> | <dígito> A|B|C ... |Z 0|1|2 ... |9 Página 4 Linguagens de Programação – Capítulo 3 Gramática para uma pequena linguagem <programa> begin <lista_stmt> end <lista_stmt> <stmt> | <stmt> ; <lista_stmt> <stmt> <var> := <expressão> <expressão> <var> + <var> | <var> - <var> | <var> <var> A|B|C Exemplo de derivação: <programa> begin <lista_stmt> end begin <stmt> ; <lista_stmt> end begin <var> := <expressão> ; <lista_stmt> end begin A := <expressão> ; <lista_stmt> end begin A := <var> + <var> ; <lista_stmt> end begin A := B + <var> ; <lista_stmt> end begin A := B + C ; <lista_stmt> end begin A := B + C ; <stmt> end begin A := B + C ; <var> := <expressão> end begin A := B + C ; B := <expressão> end begin A := B + C ; B := <var> end begin A := B + C ; B := C end Página 5 Linguagens de Programação – Capítulo 3 Gramática para instruções de atribuição <atribuição> <id> <id> := <expressão> A | B| C <expressão> <id> + <expressão> | <id> * <expressão> | ( <expressão> ) | <id> Exemplo: derivar A := B * ( A + C ) <id> := <expressão> A := <expressão> A := <id> * <expressão> A := B * <expressão> A := B * ( <expressão> ) A := B * ( <id> + <expressão> ) A := B * ( A + <expressão> ) A := B * ( A + <id> ) <atribuição> A := B * ( A + C ) Página 6 Linguagens de Programação – Capítulo 3 Árvores de Análise ( “Parse Trees” ) Exemplo: A := B * ( A + C ) <atribuição> <id> A := <expressão> <id> B * <expressão> ( <expressão> ) <id> A + <expressão> <id> C Página 7 Linguagens de Programação – Capítulo 3 Exemplo de Gramática Ambígua <atribuição> <id> <id> := <expressão> A | B| C <expressão> <expressão> + <expressão> | <expressão> * <expressão> | ( <expressão> ) | <id> Exemplo: construir duas árvores diferentes para A := B * ( A + C ) 1º) <atribuição> <id>:= <expressão> A <expressão> + <expressão> <id> B <expressão> * <expressão> <id> <id> C A Página 8 Linguagens de Programação – Capítulo 3 2º) <atribuição> <id>:= <expressão> A <expressão> * <expressão> <expressão> + <expressão> <id> <id> <id> A C B “Consertando” a Gramática <atribuição> <id> A | B| C <expressão> <termo> <fator> <id> := <expressão> <expressão> + <expressão> | <termo> <termo> * <fator> | <fator> ( <expressão> ) | <id> Página 9 Linguagens de Programação – Capítulo 3 Descrição da Sintaxe Gramática não ambígua para expressões <atribuição> <id> := <expressão> <id> A | B| C <expressão> <expressão> + <expressão> | <termo> <termo> <termo> * <fator> | <fator> <fator> ( <expressão> ) | <id> Associatividade de operadores de mesma precedência <atribuição> <id>:= <expressão> Operador de adição A <expressão>+<termo> esquerdo mais baixo que o direito. É a ordem correta se queremos associatividade à esquerda <expressão> + <termo> <fator> <fator> <termo> <id> <fator> <id> A <id> B C Página 10 Linguagens de Programação – Capítulo 3 Regra recursiva à esquerda LHS aparece no início do RHS ( vide gramática anterior ) Regra recursiva à direita LHS aparece no final do RHS Exemplo: operador de exponenciação geralmente é associativo à direita <expressão> ^ <fator> | <expressão> <fator> <expressão> <id> ( <expressão> ) | <id> A | B| C Exemplo: A ^ B ^ C = A ^ ( B ^C ) <fator> <expressão> ^ <fator> <id> <expressão> ^ <fator> <expressão> <id> A B <id> C Página 11 Linguagens de Programação – Capítulo 3 Ambigüidade em if – then – else <if_stmt> <stmt> if <exp_lógica> then <stmt> | if <exp_lógica> then <stmt> else <stmt> <if_stmt> Árvore de análise I <if_stmt> if <exp_lógica> then <stmt> else <stmt> <if_stmt> if <exp_lógica> then <stmt> if <exp> then if <exp> then <stmt> else <stmt> Página 12 Linguagens de Programação – Capítulo 3 Árvore de análise II <if_stmt> if <exp_lógica> then <stmt> <if_stmt> if <exp_lógica> then <stmt> else <stmt> if <exp> then if <exp> then <stmt> else <stmt> Página 13 Linguagens de Programação – Capítulo 3 “Consertando” a Gramática Regra Geral: o else pertence sempre ao then mais próximo e entre um then-else, não pode haver if sem else <stmt> <coincidente> | <livre> <coincidente> if <exp_lógica> then <coincidente> else <coincidente> | qualquer instrução não if <livre> if <exp_lógica> then <stmt> | if <exp_lógica> then <coincidente> else <livre> É necessário ? Sim! Não é possível fazer if <exp_lógica> then if <exp_lógica> then <algo> else <algo> Fica obrigatório derivar <stmt> <livre> if <exp_lógica> then <stmt> if <exp_lógica> then <coincidente> ... Página 14 Linguagens de Programação – Capítulo 3 BNF Estendida Extensões não aumentam o poder descritivo da BNF apenas melhoram sua legibilidade e capacidade de escrita 1) Denotar parte opcional dentro de colchetes em RHS <seleção> if ( <exp_lógica> ) <instrução> [else <instrução>] 2) Uso de chaves em RHS para indicar que o que está contido nelas pode ser: repetido indefinidamente omitido completamente a) <lista_iden> <identificador> {, <identificador>} b) <comp> begin <stmt> {<stmt>} end 3) Lidar com opções de múltipla escolha não são terminais <for_stmt> for <var> := <expressão> (to|downto) <expressão> do <stmt> Página 15 Linguagens de Programação – Capítulo 3 Comparação entre BNF e EBNF BNF <expr> <termo> <expr> + <termo> | <expr> - <termo> | <termo> <termo> * <fator> | <termo> / <fator> | <fator> EBNF <expr> <termo> <termo> {(+ | -)<termo>} <fator> {(* | /)<fator>} Página 16 Linguagens de Programação – Capítulo 3 Grafo Sintático ( grafo de sintaxe) Exemplo: Instrução if em ADA <if_stmt> if <condição> then <stmts> {<else_if>} [else <stmts>] end_if <else_if> elsif <condição> then <stmts> Legenda: não-terminais terminais if_stmt if condição then stmts end_if else_if else_if else elsif condição stmts then stmts Página 17 Linguagens de Programação – Capítulo 3 Parsers – Analisadores sintáticos para linguagens de programação yacc: um dos mais usados (yet another compiler compiler) Parsers: top – down (descendente) bottom – up (ascendente) Análise Sintática Descendente Recursiva Exemplo: <expr> <termo> <fator> <termo> {(+ | -)<termo>} <fator> {(* | /)<fator>} <id> | (<expr>) Chama uma rotina para cada não terminal void expr ( ){ termo ( ); while ( prox_token == “+” || prox_token == “-” ){ lexical ( ); termo ( ); lexical coloca o próximo token de entrada em } prox_token( var global) } Página 18 Linguagens de Programação – Capítulo 3 void termo ( ){ fator ( ); while ( prox_token == “*” || prox_token == “/” ){ lexical ( ); fator ( ); } } void fator ( ){ if (prox_token == ident ){ lexical ( ); return; } else if (prox_token == “(” ){ lexical ( ); expr( ); if (prox_token ==“)”{ lexical( ); return; } else error ( ); } else error( ); } Página 19 Linguagens de Programação – Capítulo 3 Problema para parsers descendentes recursivos: recursão à esquerda. Ex: < A > <A> +< B > (A discussão das características que autorizam o uso de parsers descendentes recursivos para uma gramática particular não será feita aqui) Gramática de atributos Dispositivos para descrever mais detalhes da estrutura das linguagens de programação do que é possível com (a BNF) uma gramática livre de contexto Existem características da estrutura das linguagens de programação difíceis/impossíveis de descrever com A BNF Exemplos: “todas as variáveis devem ser declaradas antes de serem referenciadas” “end de uma subrotina em ADA seguido de um nome deve coincidir com o nome da subrotina” Semântica Estática Análise necessária para verificar essas especificações. Pode ser feita em tempo de compilação Mais relacionada ainda com a sintaxe Página 20 Linguagens de Programação – Capítulo 3 Atributos “Variáveis” associadas a símbolos gramaticais Funções de computação de atributos Associadas a regras para especificar como os valores dos atributos são computados Funções Predicadas Símbolo gramatical X A(X) = S(X) H(X) Conjunto de atributos associados a X Atributos Sintetizados Atributos Herdados “Filhos” na árvore de análise Regra: X0 X1 ... Xn S(X0) = f ( A(X1),...,A(Xn) ) depende dos filhos H(Xj) = f ’ ( A(X0), A(X1),...,A(Xj-1) ) 1 j n depende do pai e irmãos “menores” Página 21 Linguagens de Programação – Capítulo 3 Função predicada: g ( A(X0),..., A(Xn) ) expressão booleana Se todos os valore de atributos em uma árvore de análise tiverem sido computados ela é totalmente atribuída Atributos intrínsecos atributos sintetizados de vértices folhas, cujos valores são calculados fora da árvore de análise usados para distinguir as 2 ocorrências do mesmo nãoExemplo: Ada terminal Regra sintática: <def_proc> procedure <nome_da_proc>[1] <corpo_da_proc> end <nome_da_proc>[2] Regra semântica: <nome_da_proc>[1].string = <nome_da_proc>[2].string Página 22 Linguagens de Programação – Capítulo 3 Exemplo: Compatibilidade de Tipos <atribuição> 5 <expressão> tipo_esperado tipo_efetivo := 4 2 <var> + tipo_efetivo string A <var> tipo_efetivo string 1 <var> string A 3 B 3 tipo_efetivo Legenda Regra 1 Regra 2 Regra 3 Regra 4 1 2 3 4 5 Passos da construção Página 23 Linguagens de Programação – Capítulo 3 Semântica Dinâmica Semântica propriamente dita, significado das expressões / instruções / unidades do programa Operacional Semântica Axiomática Denotacional Semântica Operacional - descreve o significado de um programa através da execução de suas instruções numa máquina ( real ou simulada,virtual ). Alterações no estado da máquina ao executar determinada instrução definem o significado desta Estado S instrução Estado S’ Estado valores de todos os registros ( células de memória ) Página 24 Linguagens de Programação – Capítulo 3 Qual computador usar ? Hardware de um computador específico interpretador puro de sua linguagem de máquina Complicado usar uma máquina real específica Usar um computador de baixo nível, implementado como uma simulação de software Tradução para linguagem De baixo nível Semântica Operacional Depende de algoritmos, não de matemática “Rodar” o programa na máquina virtual Exemplo: Instrução for do C Semântica Operacional for ( expr1 ; expr2 ; expr3 ){ ... } expr1; loop: if expr2 = 0 goto out ... expr3; goto loop; out: Página 25 Linguagens de Programação – Capítulo 3 Semântica Axiomática - baseia-se na lógica matemática Associada a um método para provar. Correção de programas que mostra a computação descrita por sua especificação. ( limitada e de aplicação difícil ) Semântica Denotacional – dentre os métodos mais empregados, é o de maior rigor matemático. Baseia-se solidamente na teoria das funções recursivas. Idéia: Associar para cada entidade da linguagem um objeto matemático e uma função que os relacione (entidade e objeto ) Há maneiras rigorosas para manipular objetos matemáticos, mas não construções de Linguagens de Programação Os objetos denotam o significado das entidades sintáticas que lhes correspondem Exemplo: <num_bin> 0 |1 | <num_bin> 0 | <num_bin> 1 Página 26 Linguagens de Programação – Capítulo 3 Árvore para 110 = 6 <num_bin> <num_bin> <num_bin> ‘1’ ‘1’ 6 ‘0’ 0 3 1 1 Função Semântica Mbin – associa os números binários aos inteiros decimais não-negativos Mbin ( ‘0’ ) = 0 Mbin ( ‘1’ ) = 1 Mbin ( <num_bin> ‘0’ ) = 2 * Mbin ( <num_bin> ) + Mbin ( ‘0’ ) Mbin ( <num_bin> ‘1’ ) = 2 * Mbin ( <num_bin> ) + Mbin ( ‘1’ ) Exemplo: <num_dec> 0|1|2| ... |9 | <num_dec> (0|1|2| ... |9 ) Mbin ( ‘0’ ) = 0 ,... , Mbin ( ‘9’ ) = 9 Mbin ( <num_dec> ‘0’ ) = 10 * Mbin ( <num_bin> ) + 0 Mbin ( <num_dec> ‘9’ ) = 10 * Mbin ( <num_bin> ) + 9 ... Página 27 Linguagens de Programação – Capítulo 3 Estado de um programa S = { <i1 , v1>,...,<in ,vn> } ij nome da variável j vj valor da variável j ( pode ser undef ) VARMAP ( i , j ) = vj Me ( <expr> , s ) = devolve o valor de <expr> no estado s ( Me ( <expr> , s ) {error} ) Ma ( x = E, s ) = devolve o estado do programa após a execução da instrução x = E Ms1 ( K, s ) = devolve o estado do programa após a execução da lista de instruções K lista de instruções Página 28