Baixe o app para aproveitar ainda mais
Prévia do material em texto
UNIT 1 1. Tanto compiladores quanto interpretadores são usados para transformar um programa escrito em uma linguagem de alto nível em código de máquina compreendido por computadores. Porém, há algumas diferenças entre eles. Sobre essas diferenças, tem-se que: II. Com o uso de compiladores, a execução do programa é separada da compilação. É executado somente depois que todo o programa de saída é compilado. IV. Com o uso de interpretadores, a execução do programa faz parte do processo de interpretação, portanto, é realizada linha por linha. Você acertou! C. II e IV. Estão corretas apenas as afirmativas II e IV. Com o compilador, você não pode alterar o programa sem voltar ao código-fonte. Os programas interpretados podem ser executados em computadores que tenham o interpretador correspondente. Com o compilador, a execução do programa é separada da compilação, somente depois que todo o programa de saída é compilado. Já com o interpretador, a execução do programa faz parte do processo de interpretação, portanto é realizada linha por linha. O interpretador lê uma única instrução e mostra o erro, se houver. Você deve corrigir o erro para interpretar a próxima linha. Já com o compilador, são exibidos todos os erros e avisos no momento da compilação. Portanto, você não pode executar o programa sem corrigir erros. 2. Compilador e interpretador são duas maneiras diferentes de traduzir um programa de linguagem de programação ou script para linguagem de máquina. São linguagens de programação compiladas: Você acertou! B. Java, C e C++. C, C++, C# e Java são linguagens de programação que usam compiladores. Já PHP, Perl, Ruby e Python utilizam interpretadores. 3. O processo de compilação de um código em linguagem de alto nível em um código de máquina ocorre de forma sequencial. A primeira etapa desse processo é a análise léxica, na qual: I. Os lexemas são montados. II. É criada uma representação intermediária como a de uma árvore. III. Os tokens são produzidos. IV. Aponta-se para uma entrada na tabela de símbolos. É correto afirmar apenas o que está em: Você acertou! D. I, III e IV. Estão corretas as afirmativas I, III e IV. A análise léxica é a primeira fase da compilação, na qual o fluxo de caracteres que compõem o programa de origem é lido e, também, ocorre o agrupamento dos caracteres em sequências significativas — lexemas. Para cada um desses lexemas, o analisador léxico produzirá um token como saída, que será repassado para a fase seguinte. Nele, o primeiro componente compreende um símbolo abstrato que será utilizado na análise de sintaxe e o segundo aponta para uma entrada na tabela de símbolos para esse token.As informações da entrada da tabela de símbolos são necessárias para a análise semântica e a geração de código. A representação intermediária, semelhante a uma árvore que representa a estrutura gramatical do fluxo de token, é criada na fase de análise sintática. 4. Compilar compreende a criação de um programa executável a partir de um código escrito em uma linguagem de programação compilada. A compilação permite que o computador execute e compreenda o programa sem a necessidade do software de programação usado para criá-lo. Em uma das etapas desse processo de compilação, temos a verificação do programa de origem, em que se observa se há consistência considerando a linguagem definida tomando como base as informações na tabela de símbolos. Qual é essa etapa? Você acertou! C. Análise semântica. A análise léxica faz uma varredura, montando os lexemas e agrupando os tokens. Na análise sintática, os tokens são utilizados e se cria uma representação intermediária semelhante a uma árvore que representa a estrutura gramatical do fluxo de tokens. A partir dessa árvore, o analisador semântico faz a verificação do programa de origem sobre se há consistência semântica considerando a linguagem definida tomando como base as informações na tabela de símbolos. Depois, o analisar semântico junta todas as informações de tipo e as guarda na árvore de sintaxe ou na tabela de símbolos, para que sejam utilizadas mais tarde durante a geração de código intermediário. Na geração de código intermediário, um compilador pode construir uma ou mais representações intermediárias, que podem ter variadas formas, ao longo do processo de tradução de um programa de origem em código de destino. Na etapa de otimização de código, tenta-se melhorar o código intermediário para que o código de destino seja melhor em termos de velocidade, ou outras coisas relacionadas ao seu tamanho (mais curto) ou consumo de energia. 5. Na prática, as etapas do processo de compilação podem ser agrupadas, e as representações intermediárias entre elas não precisam ser construídas de forma explícita. Um componente muito importante nesse processo é a tabela de símbolos. Sobre essa tabela, qual das alternativas está correta? Você acertou! E. Deve ser projetada para permitir que o compilador encontre o registro para cada nome de forma rápida. A tabela de símbolos é uma estrutura de dados com um registro para cada nome de variável, com campos para os atributos do nome. Essa estrutura deve ser projetada para permitir que o compilador encontre o registro para cada nome de forma rápida e, depois, armazene ou recupere os dados desse registro rapidamente. O registro dos nomes das variáveis utilizadas no programa de origem e a coleta das informações sobre os diferentes atributos de cada nome são funções extremamente importantes de um compilador. Ainda, a tabela não armazena outras informações que não estejam relacionadas ao programa de origem. Lembrando que a tabela de símbolos é usada por todas as fases do compilador, armazenando informações do programa de origem. 1. Na construção de compiladores e no uso de linguagens de programação em geral, expressões regulares constituem um poderoso instrumento para a validação de textos. Nesse contexto, analise a expressão regular exibida a seguir: a{1,4}b*c+ Assinale a cadeia que faz parte dessa expressão: Resposta correta. E. aabbbc. Lembrando os conceitos de expressões regulares: ? ➜ Zero ou um; * ➜ Zero, um ou mais; + ➜ UM ou mais; {n,m} ➜ De n até m. Assim, a primeira alternativa não contém o caractere c, o que a torna inválida. A outra alternativa contém um a (1 a 4 caracteres a) e um c (um ou mais c). A alternativa aaccbb está de acordo por conter dois a e dois c. As alternativas restantes contêm, respectivamente, um b e três b em sua expressão, estando de acordo com a notação b* (0 ou mais b.) 2. As palavras-chave são usadas para expressar estruturas da linguagem, como comandos condicionais, de repetição, etc. Geralmente são reservadas, não podendo ser utilizadas como identificadores. Entre as opções a seguir, considerando a linguagem de programação Java, assinale a que contém uma cadeia que pode ser usada como identificador. Resposta correta. B. SourceT. A palavra System é usada em algumas linguagens como uma chamada para alguma operação (como o System.outo.print, do Java). SourceT não faz parte das palavras reservadas, podendo ser usado como identificador normalmente. O comando whileé uma estrutura comum de repetição; null,em algumas linguagens, é usado para representar vazio ou nulo; e int é uma expressão tipada, ou seja, um tipo de variável em linguagens de programação. 3. Observe o código Java da imagem a seguir para a realização da questão: Das opções a seguir, a que representa corretamente um par (token, lexema) que pode estar relacionado com a figura é: Resposta correta. D. (ID, soma). Considerando os padrões para a análise léxica, while é reservado e não pode ser usado como identificador. Na sequência, há uma ambiguidade, em que o termo soma aparece tanto como token como lexema, sendo que soma é um identificador da linguagem. Em seguida, NUM é incompatível com x, pois este é um identificador. ID e soma estão corretos em sua combinação, e x já é um lexema, e não será usado como token para a numeração. 4. Os autômatos finitos são uma importante ferramenta que facilitaa conversão de expressão regular para um modelo melhor compreensível pela linguagem de programação, muito usado também como um analisador léxico. Considere o seguinte AFD: É correto afirmar sobre esse AFD: I. Apresenta, no mínimo, um número inteiro com dois caracteres. II. Aceita o lexema 002.002. III. Sempre se iniciará com um número. IV. Se símbolo ponto (.) já foi lido, então de certo o autômato analisador estará em algum estado final. Está correto o que se afirma em: Resposta correta. B. apenas II. Pode-se notar que o autômato inicia uma transição com um dígito (0 a 9) ou com um ponto (.). Se a entrada for, por exemplo, 5.4, o autômato aceita e realiza sua classificação conforme conveniente. Portanto, o item I está incorreto. Para o item II, o primeiro '0' ativa a transição de q0 para q1. Na sequência, 0 e 2 permanecem em q1. Ao ser lido o ponto, o autômato passa para q2 e permite qualquer quantidade de dígitos posteriormente. Portanto, o item II está correto. O item III afirma que sempre aceitará cadeias que iniciem apenas em números, mas a cadeia .05, por exemplo, é aceita e, portanto, torna o item III incorreto. Quando um ponto for lido antes de qualquer número (como primeiro caractere), o autômato passará de q0 para q3. Observe que, apesar de já se ter lido um ponto, ainda não está no estado final, pois aguarda pelo menos um dígito. Portanto, o item IV está incorreto. 5. Sobre os compiladores e suas análises léxicas e sintáticas, analise os itens a seguir: I. Um dos componentes léxicos de uma linguagem se refere às palavras reservadas. No caso da linguagem Pascal, entre as palavras reservadas, estão: AND, ARRAY, BEGIN, CONST, DIV, ELSE, FUNCTION, LABEL, MOD, NOT, OF, OR, PROCEDURE, PROGRAM,RECORD,REPEAT,TO,TYPE, UNTIL, VAR, WHILE. II. A análise léxica/sintática de uma linguagem que tem palavras reservadas tende a ser mais complexa que a de linguagens que têm apenas palavras-chave usadas também como identificadores. III. Um dos componentes sintáticos de uma linguagem se refere aos identificadores. No caso da linguagem Pascal, estes são cadeias de caracteres contendo letras ('A', ..., 'Z', 'a', ..., 'z'), dígitos ('0', ... '9') e o caractere sublinhado ('_'), podendo o primeiro caractere ser uma letra ou número. IV. Se uma regra diz que um token se estende até que seja encontrado um caractere que não faz parte dele, essa regra permitiria a um analisador léxico reconhecer em XYZ + 1 uma ocorrência de um identificador XYZ. V. Na linguagem Pascal, não se faz distinção entre maiúsculas e minúsculas em identificadores e palavras reservadas. Em C, no entanto, identificadores como TabSimb e tabsimb são distintos, permitindo que identificadores relacionados tenham formas semelhantes. Está correto o que se afirma em: Você acertou! C. I, IV e V, apenas. O item I apresenta diretrizes corretas, pois palavras reservadas também são componentes léxicos de acordo com os exemplos mostrados no item. Em II, afirma-se que palavras-chave podem ser usadas como identificadores, o que é incorreto. Em III, o texto termina dizendo que se pode iniciar por número, porém a grande maioria das linguagens não permite o início de um identificador de forma numérica. Em IV, é descrita uma regra aceita para a leitura da cadeia em questão. Em V, é fato que Pascal pode não diferenciar, mas C, Java e outras linguagens diferenciam maiúsculas de minúsculas (ainda que não se recomende nomes iguais, mesmo com diferenciação de maiúsculas, para evitar confusões em uma análise humana). 1. A análise léxica é a primeira fase do projeto do compilador. Um lexer pega o código-fonte modificado, que é escrito na forma de frases. Em outras palavras, ajuda a converter uma sequência de caracteres em uma sequência de tokens. O analisador léxico divide essa sintaxe em uma série de tokens. Ele remove qualquer espaço extra ou comentário escrito no código-fonte. Sobre conceitos relacionados à análise léxica, uma sequência de caracteres que representa uma unidade de informação no programa de origem é chamada de: Resposta correta. A. token. O token é a informação necessária que contém a sequência e a classificação para o analisador léxico. O lexema são as cadeias de caracteres que servirão como entrada para a classificação. O padrão está relacionado à descrição usada pelo token. É uma sequência de caracteres. O lexer é o analisador léxico em si e as regras se referem às expressões regulares nos critérios que o analisador vai utilizar para a aceitação e a classificação dos lexemas. 2. Um dos importantes componentes que fazem parte de uma análise léxica é a tabela de símbolos, que fornece ao analisador as relações entre lexemas e identificadores para o reconhecimento dos padrões de entrada. Considere o seguinte código, que é uma entrada para o analisador léxico: Considere que, na tabela de símbolos do analisador, existem, dentre outras, as classificações identificador, palavra-chave e operador, de modo que todos os símbolos, para simplificação, entrariam nesta última categoria. Sendo assim, qual alternativa apresenta todas as classificações de forma correta? Resposta correta. C. int - palavra_chave } - operador Na classificação fornecida, x, y e maximum são considerados identificadores. Os lexemas int, if, else e return são palavras_chave. Os símbolos restantes, pela regra mencionada, serão considerados operadores. 3. No gerador de um analisador léxico, cada expressão regular está associada a uma "frase" em uma linguagem de programação que avaliará os lexemas que correspondem à expressão regular. A ferramenta, então, constrói uma tabela de estados para a máquina de estados finitos apropriada e cria o código do programa que contém a tabela, as frases de avaliação e uma rotina que as usa apropriadamente. Sobre o analisador léxico, sua geração e aplicação, analise os itens a seguir: I. O analisador léxico é usado por navegadores da web para formatar e exibir uma página da web com a ajuda de dados analisados de JavaScript, HTML e CSS. II. Um passo importante que os lexers devem realizar (de acordo com as regras na programação) é a priorização do tokens, pois lexemas com partes semelhantes podem ser ambíguos. III. Os comentários e caracteres de tabulação não são identificados pelo analisador, não precisando impor alguma regra para que ele possa reconhecê-los adequadamente. Está correto o que se afirma em: Resposta correta. B. I e II. Item I – De fato, os navegadores utilizam o analisador léxico para formatação de URL, verificação de código JavaScript, CSS e HTML para formatação, identificação e exibição correta de uma página. Item II – Podemos citar como exemplo o lexema while, sendo que, se sua regra vier depois da dos identificadores (que aceitam iniciar com caracteres de (a-z), ele será classificado como identificador, e não como while ou palavra reservada. Item III – No gerador de um lexer, é preciso apresentar os caracteres especiais e ações que devem (ou não) ser realizadas quando esses forem encontrados. 4. O reconhecimento de cadeias de símbolos por um analisador léxico é realizado a partir de um conjunto de regras estabelecidas desde sua geração, com base nas chamadas "expressões regulares". Deseja-se gerar um analisador léxico que reconheça as palavras "Internet", "internet", "INTERNET" e exiba a frase "INTERNET CONECTADA" quando for reconhecido. O trecho e expressão regular, no JFlex, que pode representar essa regra é: Você acertou! D. %%//REGRAS (Internet|INTERNET|internet) {System.out.println("INTERNET CONECTADA");} Para que a regra esteja correta, quando falamos de caracteres, podemos usar o colchete [X|x], separando por ou ( | ). Para expressões maiores, o gerador precisará ter a regra entre parênteses, como (Internet | INTERNET | internet). Além disso, como o JFlex é construído em Java, é necessário obedecer à sintaxe da linguagem. Logo, a exibição correta na saída do sistema se dá pela expressão System.out.println (pode ser apenas System.out.print caso não se queira pular uma linha nasaída). O texto deverá estar entre aspas duplas. 5. Um analista de tecnologia da informação (TI) criou uma expressão regular para verificar quantas vezes o nome de sua empresa é citado em determinado site. Considerando que já é feito um tratamento na digitação e que sempre são passados ao analisador caracteres em maiúsculo, a expressão criada pelo analista para o JFlex foi: (EMPRESAX) {contNome++;} A variável contNome foi definida corretamente na fase das predefinições, iniciada em zero. Sobre esse caso, analise os itens a seguir: I. A expressão criada está correta, atendendo à especificação da entrada em String dentro do parêntese e realizando a contagem de acordo com as normas da linguagem. II. Não há necessidade da inserção do comando de exibição de saída, uma vez que não é um requisito desejado a priori. Marque a alternativa que apresenta a análise correta dos itens. Resposta correta. C. I e II estão corretos, uma vez que a expressão atende às regras do analisador; o requisito primordial era apenas a contagem de ocorrências do termo em questão. Os padrões estão sendo corretamente atendidos. O item I cita a expressão correta, uma vez que os parênteses estão sendo empregados corretamente com o nome a se avaliar. Como a intenção maior é a contagem de ocorrências, não é necessário imprimir algum resultado para usuário, conforme citado no item II, a não ser que fosse requisito e usado para outra operação. 1. A análise sintática é feita na fase da compilação, que analisa a estrutura da linguagem baseada em uma gramática. Alguns algoritmos são implementados dependendo da gramática. Leia as afirmações a seguir: I – O algoritmo LL(1) pode ser executado em gramáticas ambíguas e com recursividade à esquerda. II – O algoritmo com backtracking é do tipo top-down e tem um custo de execução alto. III – O algoritmo LR(0), tipo top-down, é executado em gramática com recursividade à esquerda. IV – Algoritmos baseados na precedência de operadores são do tipo ascendente e operam com ambiguidade de gramática. Quais estão corretas? Você acertou! B. II e IV. A gramática, na análise sintática, são as regras impostas à estrutura que derivam em uma linguagem. Uma linguagem de livre contexto foi derivada de uma gramática de livre contexto e alguns algoritmos são implementados de forma descendente (top-down) e ascendente (bottom-up). Sendo assim, tem-se que: I – A afirmação é falsa, pois o algoritmo da família LL não pode ser executado em gramáticas ambíguas e com recursividade à esquerda. II – A afirmação é verdadeira, pois o algoritmo com backtracking é do tipo top-down e tem um custo de execução alto. III – A afirmação é falsa, pois os algoritmos da família LR, tipo bottom-up, é executado em gramática com recursividade à esquerda. IV – A afirmação é verdadeira, pois o algoritmo que trabalha com precedência de operadores (exemplos: *,+, -, /) e potência é do tipo ascendente e pode ser executado em gramáticas ambíguas. 2. As gramáticas livres de contexto são usadas para definir linguagens de programação, bem como os seus compiladores. Uma gramática de livre contexto é definida por uma quádrupla G = { V, T, P, S}. Os elementos básicos que formam as regras da gramática são os símbolos terminais, formados por elementos de um alfabeto Σ, e os símbolos não terminais, comumente chamados de variáveis. Agora, considere as seguintes afirmações: I – Os algoritmos da família LR são executados de forma ascendente (bottom-up), ou seja, partem de uma cadeia para chegar a uma variável inicial. PORQUE II – Fazem uso de uma palavra de entrada, um parser, uma pilha de análise sintática e uma tabela construída por meio de coleções canônicas de itens. Assinale a alternativa que apresenta a análise correta das afirmações: Você acertou! B. As afirmações I e II são proposições verdadeiras e a II justifica a I. Os algoritmos da família LR podem ser LR(0), LR(1), LALR(1) e SLR(1), sendo executados de forma ascendente (bottom-up), ou seja, partem de uma cadeia para chegar a uma variável inicial e, como estrutura, fazem uso de uma palavra de entrada, um parser, uma pilha de análise sintática e uma tabela construída por meio de coleções canônicas de itens. 3. As regras sintáticas que representam a definição da linguagem, indicando como símbolos terminais e não terminais podem ser combinados, são conhecidas como regras de produção. Considere a expressão 1 + 2 * 3. Pode-se utilizar o algoritmo de precedência de operadores para fazer a análise sintática dessa sentença. Considerando o contexto, marque V para verdadeiro e F para falso: ( ) Pode existir uma ambiguidade na gramática, pois o algoritmo por precedência de operadores consegue resolver a ambiguidade. ( ) Os níveis mais baixos da árvore sintática criada pelo algoritmo de precedência têm maior prioridade na execução. ( ) O algoritmo de precedência faz uso de uma tabela de precedência, em que o símbolo identificador * tem maior prioridade entre os operadores. ( ) O look-ahead do vetor da palavra de entrada aponta para somente um caractere por vez. Agora, assinale a alternativa que apresenta a sequência correta: Resposta correta. A. V – F – V – V. O algoritmo bottom-up por precedência de operadores é utilizado em gramática em que há operações aritméticas e pode ser usado em gramáticas ambíguas, pois faz uso de uma tabela de prioridades de identificadores de operação como *, +, -, entre outros. Sendo assim, a sequência correta é: (V) A afirmativa é verdadeira, pois a ambiguidade na gramática é resolvida pela tabela de precedência de prioridade utilizada pelo algoritmo. (F) A afirmativa é falsa, uma vez que a tabela de precedência determina a prioridade dos operadores, sendo que os de maior prioridade estão localizados no nível mais baixo da árvore sintática. (V) A afirmativa é verdadeira, já que uma tabela de precedência, prioridade de operações, é utilizada pelo algoritmo no processo de parsing. (V) A afirmativa é verdadeira, pois o ponteiro do vetor conhecido como look-ahead aponta para um caractere por vez na palavra de entrada. 4. Uma linguagem é baseada em uma gramática. A gramática determina as regras a serem aplicadas em determinada linguagem. Uma gramática tem variáveis, expressões e identificadores. Considere a gramática a seguir: E=> E+T | T T=> T*F | F F => a | b Assinale a alternativa correta. Resposta correta. C. O algoritmo ascendente de precedência de operadores pode ser aplicado a essa gramática. A gramática apresenta uma recursividade à esquerda e operações matemáticas. Sendo assim, o algoritmo ascendente por precedência de operadores é o mais indicado para essa gramática, pois resolve o problema da associatividade pela tabela de precedência. As demais sentenças estão incorretas, pois o algoritmo LL(1) não é indicado para gramáticas com recursividade à esquerda. Os algoritmos da família LR não usam tabela de precedência. Já o algoritmo de precedência usa a tabela de precedência de operadores. 5. Existe uma extensa literatura sobre a teoria da gramática e a análise sintática. Possivelmente, uma das obras mais famosas nessa área seja An efficient context-free parsing algorithm, de Earley J. (1970), sendo mais orientado para o compilador de gramáticas livres de contexto. Assinale a alternativa que preenche corretamente as lacunas: Os ___________ de compiladores tendem a ser muito conservadores em sua escolha de técnicas de análise para linguagens de programação convencionais. Existem boas razões práticas para isso, mas há momentos em que técnicas diferentes, e talvez mais poderosas, são requeridas. Uma ___________ livre de contexto define recursivamente vários conjuntos de strings. Cada conjunto é denotado por um nome, que é chamado de ___________. O conjunto de não terminais é disjunto do conjunto de terminais. Um dos não terminais é escolhido para denotar a linguagem descrita pela ___________. Isso é chamado de símbolo inicial da gramática. Os sets são descritos por várias produções. Cada produção descreve algumasdas strings possíveis que estão contidas no conjunto denotado por um não terminal. Uma ___________tem a forma do conjunto de símbolos não terminais e terminais associados entre si e são as derivações de regras da gramática. Assinale a alternativa que preenche as lacunas de forma correta. Você acertou! A. desenvolvedores – gramática – não terminais – gramática – produção. Os desenvolvedores de compiladores tendem a ser muito conservadores em sua escolha de técnicas de análise para linguagens de programação convencionais. Existem boas razões práticas para isso, mas há momentos em que técnicas diferentes, e talvez mais poderosas, são requeridas. Uma gramática livre de contexto define recursivamente vários conjuntos de strings.Cada conjunto é denotado por um nome, que é chamado de não terminal. O conjunto de não terminais é disjunto do conjunto de terminais; a maioria dos não terminais é escolhida para denotar a linguagem descrita pela gramática. Isso é chamado de símbolo inicial da gramática. Os sets são descritos por várias produções. Cada produção descreve algumas das strings possíveis que estão contidas no conjunto denotado por um não terminal. Uma produção tem a forma do conjunto de símbolos não terminais e terminais associados entre si e são as derivações de regras da gramática. UNIT 2 1. Um analisador sintático cria uma árvore sintática para analisar se determinada sentença faz sentido em dada gramática ou não. Essa estrutura de dados pode ser percorrida da raiz para as folhas ou das folhas para a raiz. Dessa maneira, a forma como esse processo é feito determina se a análise sintática é descendente ou ascendente. Com relação à análise sintática descendente, assinale a alternativa correta. Você acertou! A. Esta análise é realizada processando a entrada da esquerda para a direita, buscando os símbolos terminais e os colocando nas posições corretas na árvore sintática antes de andar para a próxima regra de produção. Na análise sintática, a sentença com base na fórmula sentencial xAα é descrita por elementos de significado, denominados tokens, em que os símbolos terminais devem sempre ser levados à esquerda da produção. Nessa fórmula, A é um símbolo não terminal, α pode ser uma cadeia de símbolos terminais ou não terminais e x é uma cadeia de símbolos terminais e que pode ser nula, ou seja, a cadeia pode se iniciar com um símbolo não terminal. Uma gramática deve ser completamente descrita para ser funcional. Caso contrário, o analisador sintático pode ou não conseguir interpretar uma sentença devido à falta da produção relevante ou pode ser que uma produção seja interpretada de forma errada no caso de ambiguidade, quando é possível criar mais de uma árvore sintática para a mesma sentença. Esse tipo de construção pode gerar erros de precedência de operadores, dependendo de como a árvore sintática é criada. Assim, essa análise é realizada processando a entrada da esquerda para a direita, buscando os símbolos terminais e os colocando nas posições corretas na árvore sintática antes de andar para a próxima regra de produção. 2. Um dos algoritmos utilizados para a análise sintática de uma expressão é o LL, que percorre a sentença da esquerda para a direita. Com relação ao algoritmo LL, assinale a alternativa correta. Você acertou! C. É utilizado para a análise sintática e seu nome vem das suas etapas: inicialmente, verificando da esquerda para a direita e a derivação mais à esquerda. Com relação ao algoritmo LL, é possível afirmar que ele é utilizado para a análise sintática e seu nome vem das suas etapas: inicialmente, verificando da esquerda para a direita e a derivação mais à esquerda. O algoritmo LL realiza a análise da gramática elaborando a árvore sintática da expressão a ser analisada e esse processamento é feito sempre da esquerda para a direita. Para melhorar o processamento das expressões, alguns cuidados devem ser adotados, como a fatorização à esquerda para eliminar a ocorrência de uma sequência de símbolos em duas ou mais regras de produção, e a manipulação das produções para manter à esquerda sempre um símbolo terminal, eliminando, assim, a recursão à esquerda. Ainda, deve se garantir que a gramática utilizada não é ambígua, ou seja, que não é possível criar mais de uma árvore sintática por sentença. O funcionamento do algoritmo, entretanto, é simples; dado um símbolo não terminal N qualquer, com o símbolo c inicial, a regra de produção que se deve escolher é uma regra em que c seja um dos símbolos iniciais ou em que é o símbolo seguinte a uma produção vazia. 3. Uma das estratégias para a construção da árvore sintática de uma sentença é a análise sintática ascendente. Como relação a essa técnica, assinale a alternativa correta. Você acertou! B. A árvore sintática da sentença é criada a partir das folhas da árvore e chegando na raiz. Caso seja possível realizar essa operação, a sentença é válida. Para a criação da árvore sintática utilizando os algoritmos de análise sintática ascendente, os algoritmos percorrem a sentença da direita para a esquerda, tentando transformar os símbolos terminais em não terminais por meio de processos de deslocamento e redução; até mesmo o sentido de processamento diferente do padrão de leitura humana se aplica a todo tipo de sentença. O resultado final, porém, é que a sentença pode não ser válida dentro da gramática. Assim, a árvore sintática é criada das folhas para a raiz e a gramática utilizada é a mesma que a usada para outras formas de processamento. Ou seja, não precisa ser colocada em um padrão para esse processamento. O elemento central desse processo é o autômato de pilha, também denominado de PDA, uma vez que consegue executar as funções de movimentação e redução dos tokens da sentença utilizando uma pilha para o seu processamento, com a aplicação das duas funções características dessa estrutura de dados: a inserção de elementos na pilha; e a retirada de elementos da pilha. 4. Especificar uma linguagem, com sua gramática, léxico e sintaxe, é apenas o primeiro passo para o desenvolvimento da linguagem. A próxima etapa é a criação de um compilador para essa linguagem, sendo tipicamente realizada com o auxílio de ferramentas para a construção de analisadores sintáticos. Com relação a esses componentes, assinale a alternativa correta. Resposta correta. C. As ferramentas de construção de analisadores semânticos são utilizadas para gerar formas de interpretar e validar sentenças criadas a partir de uma gramática. Essas ferramentas foram criadas para criar gramáticas e produções próprias, desenvolver uma linguagem e implementá-la utilizando esses softwares. Essa liberdade é fornecida ao usuário devido ao fato de que a ferramenta provê liberdade para o desenvolvedor inserir as regras de produção que desejar nos arquivos-fonte. Tanto o ANTLR, desenvolvido sobre a máquina virtual Java e tem características e sintaxe similares às orientadas a objetos, quanto o Yacc, desenvolvido para o Linux e que tem características de linguagem imperativa, são distribuídos sob licenças de software livre. Portanto, podem ser utilizados para qualquer propósito ou aplicação. Ao se utilizar esse método para a criação de elementos capazes de processar uma gramática, é possível implementar linguagens e aplicar esses módulos em novos softwares sem precisar realizar do zero a implementação de um analisador sintático. Afinal, esses componentes permitem que sejam escritas funções que serão executadas quando a sentença for processada. O código da definição de uma linguagem tem um campo em que é possível inserir as funções que o sistema irá executar ao se deparar com a produção em questão. 5. A ambiguidade em gramáticas é a possibilidade de se criar mais de uma árvore sintática a partir de uma sentença. Com relação a gramáticas ambíguas, assinale a alternativa correta. Você acertou! E. A ambiguidade é indesejada em linguagens. Quando detectada, deve ser corrigida ou pode não ser possível dar o significado correto a um programa. As gramáticas ambíguas estão sujeitasà criação de softwares com funcionamento imprevisível, uma vez que pode não ser possível verificar exatamente qual é a função esperada pelo desenvolvedor do código-fonte. A ambiguidade não é uma característica intrínseca às linguagens livres de contexto e é indesejada, pois impede que o compilador determine quais funções o código-fonte deve executar. Ela impede o compilador de determinar exatamente o que um código-fonte diz, já que um mesmo token pode estar ligado a mais de uma expressão. Logo, caso exista essa característica em uma linguagem, deverá ser eliminada por meio das operações corretas. Caso uma produção apresente ambiguidade, a gramática toda é considerada ambígua e deve ser revista. Como uma produção nunca é aplicada isoladamente, a produção ambígua já é o suficiente para determinar a gramática inteira como ambígua. 1. A utilização de expressões regulares pode facilitar a validação de dados, a procura por palavras em determinado texto, o desenvolvimento de analisadores sintáticos, etc. Além disso, as expressões regulares aumentam a produtividade e reduzem o tempo de busca em função dos padrões estabelecidos. Em relação ao conceito e às características das expressões regulares, analise as afirmações a seguir: I. As expressões regulares foram desenvolvidas para auxiliarem a comunicação somente de desenvolvedores e usuários quanto aos padrões definidos. II. Um lexema pode ser definido como um conjunto de símbolos sequenciais cujo padrão é aceito por uma linguagem de programação. III. Uma expressão regular, baseada em um conjunto de caracteres, consegue identificar diferentes padrões em um texto, como um número de IP (10.49.1.1) e o e-mail de um usuário (xxx@xxx.com.br). IV. Uma linguagem baseada em expressões regulares não pode conter somente a palavra vazia. Agora, assinale a alternativa que apresenta a resposta correta. Você acertou! B. Apenas as afirmativas II e III estão corretas. Apenas as afirmativas II e III estão corretas. A afirmativa I está incorreta, pois as expressões regulares auxiliam na comunicação entre os usuários, desenvolvedores e as máquinas. A afirmativa II está correta, pois a sequência de caracteres reconhecidos por um padrão é chamada de "lexema". A afirmativa III está correta, pois, dada uma string, uma expressão regular consegue reconhecer, encontrar ou validar um padrão em um texto, como data, horário, número de IP, nome, RG e CPF de uma pessoa, declaração de uma função(), dados que estão entre <tags></tags>, endereço de e-mail, um nome de usuário ou senha específicos, entre outros padrões. A afirmativa IV está incorreta, pois uma linguagem L(r), sendo r uma expressão regular, pode ser definida a partir de uma linguagem que contém somente a palavra vazia – {Ɛ}. 2. A linguagem de programação C# utiliza as expressões regulares e as gramáticas livres de contexto para validar expressões e a gramática da linguagem escrita no arquivo .c. Assinale a alternativa correta que apresenta a biblioteca de expressões regulares, a unidade de compilação e o que a gramática gera ao ser compilada em uma linguagem C#. Você acertou! A. System.Text.RegularExpression, compilation_unit e árvore sintática. A biblioteca de expressões regulares consiste em (System.Text.RegularExpression), a unidade de compilação é (compilation_unit), e o que a gramática gera ao ser compilada é a árvore sintática da linguagem C#. System. SyntaxNode não é uma biblioteca de expressões regulares, e sim a representação de nós da árvore sintática. System.SyntaxTrivia não é uma biblioteca de expressões regulares, e sim a representação de trivialidades da linguagem de programação. SyntaxToken_unit não é uma unidade de compilação, e sim os tokens da sintaxe, e Regex não é o que a gramática gera ao ser compilada, e sim uma classe da linguagem C#. System.SyntaxTrivia não é uma biblioteca de expressões regulares, e sim a representação de trivialidades da linguagem de programação; e Regex não é o que a gramática gera ao ser compilada, e sim um classe da linguagem C#. Por fim, SyntaxToken_unit não é uma unidade de compilação, e sim os tokens da sintaxe. 3. Uma gramática pode ser especificada com base em símbolos terminais e não terminais em sua convenção. Com relação à classificação de símbolos de uma gramática, avalie as questões a seguir e associe as colunas de acordo com as representações adotadas para símbolos terminais e não terminais. Resposta correta. A. I - 1; II - 2; III- 2; IV - 2; V - 1. A associação correta é: I - 1; II - 2; III- 2; IV - 2; V - 1. Operadores - +, -, *, /, por exemplo, são usados como símbolos terminais da gramática. Letras maiúsculas, como E e F, são usadas como símbolos não terminais da gramática. Nomes em itálico formados com letras minúsculas, como cmd ou expr, são usados como símbolos não terminais da gramática. A letra S representa o símbolo de partida de uma gramática, sendo representado por símbolos não terminais. Letras minúsculas do início do alfabeto, como a e c, são usadas como símbolos terminais da gramática. 4. Uma gramática pode produzir a mesma cadeia de caracteres de diferentes formas, gerando uma ambiguidade. Considerando o contexto apresentado, avalie as seguintes asserções sobre a ambiguidade da gramática livre de contexto: I. A ambiguidade é gerada em uma gramática livre de contexto quando uma cadeia de caracteres admite mais de um caminho ou mais de uma árvore sintática. PORQUE II. É interessante utilizar uma gramática sem ambiguidade em grande parte das aplicações. Porém, em certas situações, é conveniente utilizar a ambiguidade levando em consideração uma escolha criteriosa dessa ambiguidade. A respeito dessas asserções, assinale a opção correta. Resposta correta. B. As asserções I e II são proposições verdadeiras, mas a II não é uma justificativa da I. As asserções I e II são proposições verdadeiras, mas a II não é uma justificativa da I. A asserção I é verdadeira, pois uma gramática é considerada ambígua se existe uma mesma sentença com duas ou mais derivações à direita ou à esquerda. A asserção II é verdadeira, uma vez que em certas aplicações é interessante haver ambiguidade, porém ela deve ser escolhida de forma criteriosa. 5. As gramáticas livres de contexto (GLC) auxiliam nas construções das gramáticas atuais das linguagens de programação e de compiladores. Seja L uma linguagem livre de contexto, com G = ({S, T, B, C}, {b, c}, S, P}), em que: S --> BT | TC T --> bTc | Ɛ B --> bB | b C --> cC | c e a cadeia bbbc. Assinale a alternativa que contém a derivação correta da linguagem livre de contexto: Resposta correta. A. S --> BT --> bBT --> bbT --> bbbTc --> bbbc. Realizando as substituições com base na gramática, a derivação final é S --> BT --> bBT --> bbT --> bbbTc --> bbbc. Nas derivações (S --> TC --> TCc --> bTCc --> bbTCb --> bbbc) não existe a regra de produção C --> Cc. Nas derivações (S --> TC --> bTC --> bTCc --> bbTCc --> bbbc) não existe a regra de produção C --> Cc. Nas derivações (S --> BTC --> bTC --> bTCc --> bcTCc --> bbbc) não existe a regra de produção S --> BTC. Nas derivações (S --> BT --> cBT --> bcT --> bcBc --> bbbc) não existe a regra de produção B --> cB. 1. Conforme Cooper e Torczon (2014), antes que o compilador possa traduzir uma expressão para código executável da máquina-alvo, faz-se necessário que ele entenda tanto a sua forma, ou sintaxe, quanto o seu significado, ou semântica. Desse modo, o front-end determina se o código de entrada está bem formado e, ao verificar que o código é válido, cria uma representação dele na representação intermediária do compilador; em caso negativo, ele informa o usuário por meio de mensagens de erros de diagnóstico com o intuito de possibilitar a identificação de problemas no código. Tendo em vista o apresentado, considere os códigos elaborados na linguagem Java apresentados a seguir: I. String nome = “compiladores”; int num = 1; System.out.print(nome / num); II. int num; num++; III. int nome = “compiladores”; IV. Strin endereço = “Rua...”; V. int valores[] = new int[5]; valores[5] = 500; Dados os códigos apresentados, faça a relação entre eles e as opções a seguir: ( ) A variável não foi inicializada. ( ) Referência desconhecida. ( ) Índice inválido. ( ) Os tipos não são compatíveis. ( ) O operador utilizado não suporta a operação. A opção que representa a relação correta é: Você acertou! E. II, IV, V, III, I. Para o método de resolução, é preciso excluir as opções incorretas à medida que cada código for analisado. Por fim, haverá apenas a opção correta: I. String nome = “compiladores”; int num = 1; System.out.print(nome / num); Note que, no código apresentado, há a tentativa da divisão, a partir do símbolo /, de valores do tipo string com o tipo int; assim, o operador de \ não suporta a operação, pois não há a possibilidade de tal divisão na linguagem Java e em demais linguagens fortemente tipadas. Desse modo, é possível excluir as opções que não têm relação com o citado. II. int num; num++; No segundo código apresentado, a variável num é criada e, em seguida, é incrementada, porém não é inicializada. Desse modo, há um erro relacionado à variável não inicializada. III. int nome = “compiladores”; No terceiro código, é elaborada uma variável do tipo int, porém há a tentativa de inserção de um valor string nela. Com isso, há um erro de tipos não compatíveis. IV. Strin endereço = “Rua ...”; Nesse caso, provavelmente houve erro de digitação, pois o tipo string é inserido faltando a letra g, isto é, Strin. Nesse caso, há uma referência desconhecida, pois o compilador não reconhece a palavra Strin. V. int valores[] = new int[5]; valores[5] = 500; Note que, no código apresentado, há a criação de um vetor denominado "valores" que tem cinco posições. Na linguagem de programação Java, nesse caso, a primeira posição do vetor tem índice 0, e a última, índice 4. Desse modo, não existe a posição 5 nesse vetor. Isso posto, há um erro relacionado a índice inválido. Assim, a opção correta é: II, IV, V, III, I. 2. Uma gramática de atributos pode ser composta por atributos herdados ou sintetizados. No primeiro caso, dada uma árvore de derivação, ela terá dependências de pai para filho ou de irmão para irmão. No segundo caso, suas dependências estão relacionadas do filho para o pai. No exemplo apresentado a seguir, todos os atributos são sintetizados, pois todas as dependências da gramática estão relacionadas do filho para o pai. Nesse caso, a gramática é S-atribuída. S -> S @ A {S.valor = S.valor * A.valor} S -> A {S.valor = A.valor} A -> A $ B {A.valor = A.valor + B.valor} A -> B {A.valor = B.valor} B -> num {B.valor = num.valorlexico} Dada a gramática apresentada anteriormente, qual o valor final de S.valor para uma cadeia como 7 @ 5 $ 9 @ 7 $ 4? Resposta correta. D. 1078. Uma das formas de verificar a opção correta é por meio da árvore de recorrência. Veja a árvore a seguir: 3. O processo de análise de um compilador pode ser dividido em três partes: análise léxica, sintática e semântica. Na primeira, conforme Ricarte (2008), o compilador realiza uma análise a partir da identificação dos caracteres individuais, dos símbolos básicos válidos para a linguagem, entre outros. Na segunda, tenta compor, a partir da estrutura de cada um dos blocos básicos, a estrutura geral do arquivo de entrada. Por fim, a terceira tem como objetivo buscar verificar a coerência entre os fragmentos de códigos sintaticamente corretos. Dado o exposto, analise o código a seguir, elaborado na linguagem Java: public class Exercicio3 { public static void main(String[] args) { int min = 9; int m@x = 11; // erro 1 Intervalo(min, m@x); // erro 2 } public static void Intervalo(int min; int max) { // erro 3 int vetor[] = new int[max]; for (int i = min; i <= max; i++){ vetor[i] = i; // erro 4 System.out.println(i); } System.out.println(i); // erro 5 } // erro 6 Perceba que, para cada linha que tem determinado tipo de erro, existe um comentário deixando-o em destaque e a sua numeração (erro 1, erro 2, ...). Considerando os erros, marque a opção que representa apenas erros semânticos: Você acertou! B. Erros 4 e 5. Para responder esta questão, deve-se avaliar cada erro. Veja: Erro 1: int m@x = 11; Note que, nesse erro, há uma variável denominada m@x. Nesse erro, o símbolo @ não é um símbolo válido a ser utilizado em variáveis na linguagem Java. Portanto, trata-se de um erro léxico. Erro 2: Intervalo(min, m@x); Nesse código, é repetido o mesmo erro anterior, ao fazer uso da palavra m@x. Erro 3: public static void Intervalo(int min; int max) { Perceba que, nesse código, há a utilização de um símbolo de ";" em vez de um símbolo de ",". Desse modo, há um erro de sintaxe. Erro 4: vetor[i] = i; Visivelmente esse código não está incorreto. No entanto, ao analisar que o vetor, nesse caso, não tem a posição 11, que se refere ao valor max, contata-se, então, um erro semântico. Erro 5: System.out.println(i); Novamente, ao analisar somente essa parte do código, não se torna visível qualquer erro. No entanto, ao analisar uma parte maior do código, é possível perceber que a variável i não existe no escopo no qual ela é utilizada. Desse modo, há um erro semântico. Erro 6: } Nesse caso, há a falta de mais um símbolo de }. Portanto, temos um erro sintático. Portanto, as opções que se referem a erros semânticos são os erros 4 e 5. 4. Uma solução normalmente utilizada para especificar a semântica de determinada linguagem é dada a partir da utilização de gramática de atributos. Esta consiste em atribuir ações semânticas às produções de modo que, a cada produção executada, uma ação relativa a ela também seja executada. Considere que determinado desenvolvedor de compiladores elabore uma gramática de atributos conforme apresentada a seguir: S -> S * A S -> A + S S -> A A -> A - B A -> B B -> dígito Considerando a gramática elaborada pelo desenvolvedor, marque a opção verdadeira: Resposta correta. E. - tem a maior precedência, e *, a menor. Para verificar a opção correta desta questão, é preciso partir da solução de um problema que contenha as três operações aritméticas apresentadas na gramática. Para isso, considere uma entrada igual a 4 - 5 + 1 * 3. Assim, a árvore sintática ficará da seguinte forma: 5. Lemone (2010) apresenta que uma gramática de atributos é uma gramática livre de contexto com a adição de atributos e regras de avaliação de atributos, chamadas de "funções semânticas". Assim, uma gramática de atributos pode especificar a semântica e a sintaxe. Desse modo, os atributos são variáveis para as quais os valores são atribuídos, e cada variável de atributo está associada a um ou mais não terminais ou terminais da gramática. Quanto às regras semânticas, Engelen (2018) apresenta que elas são usadas por um compilador para impor a semântica estática e/ou para produzir uma árvore de sintaxe abstrata enquanto analisa o fluxo de token. Dado o exposto, analise a gramática a seguir e informe qual das opções representa um valor final para S = 1700. Produção Regra semântica S -> S1.digito S.valor = S1.valor * 10 + digito.valor S -> digito S.valor = digito.valor digito -> 0 digito.valor = 0 digito -> 1 digito.valor = 1 digito -> 2 digito.valor = 2 digito -> 3 digito.valor = 3 digito -> 4 digito.valor = 4 digito -> 5 digito.valor = 5 digito -> 6 digito.valor = 6 digito -> 7 digito.valor = 7 digito -> 8 digito.valor = 8 digito -> 9 digito.valor = 9 Resposta correta. E. 1700. Como forma de reproduzir o resultado do processamento a partir de uma entrada igual a 1700, é preciso considerar a seguinte árvore de derivação: 1. Ao realizar a validação da semântica em uma linguagem de programação, as expressões precisamretornar um valor verdadeiro ou falso de acordo com o resultado da sentença. Dependendo da situação, é possível ter regras semânticas, verificadas em tempo de compilação ou em tempo de execução do programa. Em relação ao conceito e às características da semântica dinâmica, analise as afirmações a seguir. I. Na semântica operacional, as modificações realizadas no estado da máquina não servem para gerar o significado de uma instrução. II. A criação de um procedimento que evidencia a exatidão dos programas compõe a semântica denotacional. III. O método utilizado para descrever o significado dos programas com base na recursividade é a semântica denotacional. IV. A semântica operacional natural esconde detalhes ao realizar um passo computacional maior em um programa. Agora, assinale a alternativa que apresenta a resposta correta. Você acertou! C. Apenas as afirmativas III e IV estão corretas. I. A afirmativa está incorreta, pois a semântica operacional descreve o comportamento de um programa ao ser executado em um computador real ou hipotético. As modificações no estado da máquina, como a localização de memória e os valores dos registradores, determinam o significado de uma instrução. II. A afirmativa está incorreta, já que a semântica axiomática foi definida com a criação de um método que comprova a exatidão dos programas que exibem a computação descrita por sua especificação, quando esta pode ser desenvolvida. III. A afirmativa está correta, pois a semântica denotacional é baseada na teoria da função recursiva. IV. A afirmativa está correta, pois a semântica natural torna mais simples as notações e esconde detalhes ao realizar um passo computacional maior (big_step), entre o estado inicial e o estado final de um programa. 2. Ao executar um programa na linguagem Pascal, o compilador, após verificar a sintaxe, pode sinalizar os erros semânticos. Observe o código a seguir: var x: String; x := 3 + ‘7’; Assinale a alternativa correta que apresenta o erro semântico encontrado neste código. Você acertou! A. Erro de compatibilidade de tipos de dados. O erro semântico do código é o erro de compatibilidade de tipos de dados, uma vez que foi atribuído a uma variável do tipo string, uma operação matemática. Não é possível ser erro de posição inexistente em um vetor, uma vez que o trecho de programa não trata sobre vetores; além disso, a variável foi declarada e está dentro do escopo correto do programa. 3. A semântica denotacional constitui o método mais rígido para descrever o significado dos programas, sendo baseado na teoria da função recursiva. Considerando o contexto apresentado, avalie as seguintes asserções sobre a semântica denotacional. I. A semântica denotacional utiliza uma função que estrutura a entidade e o objeto matemático. PORQUE II. O objeto matemático representa o significado das entidades sintáticas correspondentes. A respeito dessas asserções, assinale a opção correta. Resposta correta. A. As asserções I e II são proposições verdadeiras, e a II é uma justificativa da I. I. Asserção verdadeira, pois a semântica denotacional define para cada entidade da linguagem um objeto matemático, como uma função que faça o mapeamento da entidade e do objeto matemático. II. Asserção verdadeira e justifica a I, uma vez que o método é chamado de denotacional, porque os objetos matemáticos representam o significado das entidades sintáticas correspondentes. 4. Ao executar um programa, o compilador, após verificar a sintaxe, pode sinalizar os erros semânticos. Um erro semântico ocorre quando um programa está certo sintaticamente, mas o resultado gerado não está correto. Analise o seguinte trecho do programa escrito em C#: public class Teste : Teste { int k = “xyz” * false; int y = 1.22; void x(int c, int c) { int a; int p = 14 * a; } } Em relação aos erros semânticos apresentados no trecho do programa acima, analise as afirmações a seguir. I. Não é possível uma classe herdar de si mesma — class Teste : Teste. II. Não é possível multiplicar uma string por um boolean. III. O número 1.22 não pode ser atribuído como um inteiro. IV. Na instrução int p = 14 * a, do método x, a variável a não foi inicializada. V. Não é possível declarar parâmetros com o mesmo nome e com o mesmo tipo. Agora, assinale a alternativa que apresenta a resposta correta. Resposta correta. E. Todas as afirmativas estão corretas. I. A afirmativa está correta, pois não é possível uma classe herdar de si mesma e, para que a classe esteja correta, é preciso alterar o nome da classe herdada como class Teste : TesteNovo. II. A afirmativa está correta, visto que não é possível multiplicar uma string por um boolean, sendo necessário corrigir a expressão para comparar um tipo booleano com outro tipo booleano, por exemplo. III. A afirmativa está correta, pois o número 1.22 não pode ser atribuído como um inteiro, sendo necessário alterar o valor de y = 1, por exemplo. IV. A afirmativa está correta, pois, na instrução int p = 14 * a, do método x, a variável a não foi inicializada, por exemplo, a = 10. V. A afirmativa está correta, já que não se pode declarar parâmetros com o mesmo nome e com o mesmo tipo, sendo necessário renomear o parâmetro void x(int c, int d). 5. As regras da semântica dinâmica são classificadas em operacional, denotacional e axiomática. Com relação à classificação da semântica dinâmica, avalie as questões a seguir e associe os itens numerados de acordo com as representações adotadas para semântica operacional, denotacional e axiomática. 1. Semântica operacional 2. Semântica axiomática 3. Semântica denotacional I. Utiliza expressões lógicas, conhecidas como asserções. II. É um método representado por objetos matemáticos definindo o significado dos atributos sintáticos. III. Esse método é baseado em funções recursivas. IV. É utilizada em manuais das linguagens e livros didáticos. V. Esse método é classificado em natural e estrutural. Assinale a alternativa que apresenta a associação correta entre as colunas. Você acertou! A. I – 2; II – 3; III – 3; IV – 1; V – 1. A associação correta é: I – 2; II – 3; III – 3; IV – 1; V – 1. A semântica axiomática utiliza expressões lógicas chamadas de asserções ou predicado, que definem as restrições a variáveis. O método é chamado de denotacional porque os objetos matemáticos representam o significado das entidades sintáticas correspondentes. A semântica denotacional pode ser usada para provar a correção de um programa, por meio de um método para pensar sobre o programa, além de descrever uma linguagem precisa. A semântica operacional é pouco utilizada pelos desenvolvedores da linguagem e classifica-se em natural, que esconde detalhes ao realizar um passo computacional maior (big_step), e estrutural, que define detalhes da execução ao tomar um passo menor (small_step). UNIT 3 1. Os compiladores são organizados em uma sequência de fases. À medida que o compilador deriva conhecimento sobre o código que compila, precisa transmitir essa informação de uma fase para outra. Ele necessita, então, de uma representação para todas as informações que deriva sobre o programa-fonte. Chama-se isso de representação intermediária (IR). Sobre as IRs geradas por um compilador, considere as seguintes afirmações: I — A árvore sintática é a forma de representação intermediária mais próxima da estrutura do código-alvo. II — A árvore sintática abstrata é uma representação contendo mais informações sobre as produções gramaticais realizadas, se comparada com a árvore sintática comum. III — Um dos motivos de se utilizar o código de três endereços como representação intermediária é a sua facilidade de tradução para linguagens de máquina. Assinale a alternativa que indica as afirmações corretas: Você acertou! C. Apenas a afirmação III está correta. Sobre as afirmações feitas, apenas a afirmação III é correta. O código de três endereços é muito parecido com os tipos de operaçãoem linguagem Assembly, o que o torna uma boa forma de código intermediário. Assim, um dos motivos de se utilizar o código de três endereços como IR é a sua facilidade de tradução para linguagens de máquina. A afirmação I é incorreta, pois uma árvore sintática é uma representação do código-fonte, e não do código-alvo. A afirmação II também está incorreta, pois é justamente o contrário: a árvore sintática comum tem mais nós relativos aos símbolos das produções gramaticais do que a árvore sintática abstrata, que representa apenas os símbolos terminais usados nas produções. 2. A função de um compilador é traduzir programas escritos em uma linguagem-fonte em um dos programas equivalentes em outra linguagem-alvo. Para isso, o compilador é dividido em fases, que implementam diversas técnicas. A ___________________ é uma técnica de tradução aplicada em compiladores que consiste em inserir regras de tradução nas produções gramaticais da linguagem-fonte, de modo que o percurso na árvore sintática executa a tradução desejada. Qual das alternativas a seguir preenche melhor a lacuna na afirmação? Você acertou! D. Tradução dirigida pela sintaxe. A frase correta é: A tradução dirigida pela sintaxe é uma técnica de tradução aplicada em compiladores que consiste em inserir regras de tradução nas produções gramaticais da linguagem-fonte, de modo que o percurso na árvore sintática executa a tradução desejada. A análise léxica consiste em identificar tokens da linguagem-fonte do texto do programa-fonte. Os tokens reconhecidos são usados na análise sintática para validar essa sequência com a gramática da linguagem-fonte. Entretanto, nenhuma tradução é feita. A otimização de código consiste em tornar o código-alvo mais eficiente, mas isso independe da linguagem-fonte. A interpretação de código consiste em executar instruções na máquina-alvo a partir da análise das instruções do código-fonte. 3. Compiladores podem gerar uma variedade de representações intermediárias para representar as construções sintáticas da linguagem-fonte. Cada representação tem a finalidade de registrar informações de uma fase da compilação para as fases seguintes. ___________________ é uma representação intermediária que consiste em representar as produções gramaticais de forma mais sucinta, de modo que apenas os símbolos terminais das produções gramaticais compõem suas construções. Qual das opções a seguir completa apropriadamente a lacuna da afirmação? Resposta correta. B. Árvore sintática abstrata. A árvore sintática abstrata é uma representação intermediária que consiste em representar as produções gramaticais de forma mais sucinta, de modo que apenas os símbolos terminais das produções gramaticais compõem suas construções. Ela é uma versão resumida da árvore sintática. A árvore sintática também representa símbolos não terminais em suas construções. A árvore sintática anotada consiste em uma árvore sintática contendo em suas ramificações regras de tradução, além de terminais e não terminais. Um grafo de dependências é um grafo que modela o fluxo de valores desde suas definições até seus usos em um fragmento de código. O código de três endereços é uma representação de código intermediário já voltada para geração de linguagem-alvo. 4. Na notação tradicional para as expressões aritméticas (notação infixa), o operador aparece entre os seus dois operandos. Já na notação pós-fixa, também conhecida como notação polonesa inversa, o operador é colocado após os seus dois operandos. Considere o esquema de tradução de expressões para a forma pós-fixa para a gramática a seguir: EXPR → EXPR + TERMO { imprimir (“+”) } EXPR → EXPR – TERMO { imprimir (“-”) } EXPR → TERMO TERMO → TERMO * FATOR {imprimir (“*”) } TERMO → TERMO / FATOR {imprimir (“/”) } TERMO → FATOR FATOR → ( EXPR ) FATOR → 0 { imprimir (“0”) } ... ... FATOR → 9 { imprimir (“9”) } Qual será o resultado da sua execução para a entrada 2 + 4 * (5 - 3)? Você acertou! D. 2 4 5 3 - * +. A árvore sintática anotada para a entrada 2 + 4 * (5 - 3), usando a gramática proposta, é mostrada na figura abaixo: 5. As fases de um compilador podem ser classificadas em dois grupos: análise e síntese. Na fase de análise, encontram-se as fases de análise léxica, análise sintática e análise semântica. Já na fase de síntese, encontram-se as fases de geração de código intermediário, otimização e geração de código. Sobre a fase de geração de código intermediário de um compilador, assinale a alternativa correta: Você acertou! C. A fase de geração de código intermediário de um compilador é a fase em que ocorre a transformação da árvore sintática em uma linguagem intermediária, que é mais próxima da linguagem-objeto do que o código-fonte, mas ainda não se trata do código-final. Análise léxica é a fase em que se reconhecem os tokens da linguagem-fonte por meio da leitura dos caracteres do código-fonte. Análise sintática é a fase em que se constrói uma árvore sintática para validar a estrutura dos tokens reconhecidos do código-fonte. Geração de código intermediário é a fase em que ocorre a transformação da árvore sintática em uma linguagem intermediária, que é mais próxima da linguagem-objeto do que o código-fonte, mas ainda não se trata do código final. Otimização de código é a fase em que se procura produzir um código que execute com mais eficiência. Geração de código final é a fase em que se gera o código para ser executado diretamente na máquina-alvo. 1. A otimização do código deve ser capaz de manter o mesmo resultado, ainda que partes do código sejam eliminadas, substituídas ou modificadas. Ainda assim, durante a otimização deverá ser mantido(a): Resposta correta. B. o significado semântico. O significado semântico não deverá ser alterado, já que o código deverá cumprir o mesmo resultado. A sequência das instruções pode ser alterada a fim de aproveitar melhor os registradores e outros motivos. As instruções utilizadas nem sempre são as mais adequadas. Muitas vezes um processador pode disponibilizar instruções específicas para um tipo de problema que será solucionado de maneira mais eficaz. Os registadores alocados não farão diferença se o significado semântico e os resultados obtidos forem os mesmos e podem ser alterados para reduzir a necessidade de carregamentos e armazenamentos em memória. As expressões podem ser alteradas por diversos motivos: simplificação do cálculo, aproveitamento de expressões comuns, uso de recursos específicos em determinados processadores. 2. A otimização pode ser empregada em diferentes etapas da compilação, podendo ser técnicas dependentes ou independentes de máquina. Qual das técnicas abaixo é classificada como dependente de máquina? Você acertou! A. Otimização de pequena escala. A otimização de pequena escala busca por pequenos trechos de código de máquina que podem ser simplificados, sem se preocupar com o contexto maior. Essa técnica requer o conhecimento dos recursos disponibilizados pela máquina. A eliminação de código morto é realizada diretamente sobre o código intermediário e independe de máquina. A movimentação de código analisa o código dentro de laços e blocos que são frequentemente utilizados e independe de máquina. A avaliação em tempo de compilação busca elementos ou expressões constantes que podem ter seu valor substituído durante a compilação, já que não serão alterados durante a execução, e independe de máquina. A análise léxica é parte do processo de compilação, mas não consiste em uma técnica de otimização. 3. Algumas técnicas de otimização de código podem simplificar trechos tão pequenos de código como uma única expressão. Qual a técnica de otimização de código utilizada no exemplo a seguir? Código original: x = a / 4; Código otimizado: x = a >> 2; Você acertou! D. Redução de força. A expressão foi alterada substituindo o operador utilizado por um que facilitasse o cálculo. Já que 4 é um múltiplo binário, pode-se efetuar uma divisão por esse valor movimentando todos os bits da variável a duas casas para a direita. Assim, a técnicautilizada foi de redução de força. 4. Na propagação de variável, elementos comuns entre expressões podem ser utilizados para eliminar trechos de código desnecessários ou reduzir expressões. Utilizando a técnica de propagação de variável, assinale a alternativa que apresenta a solução correta de otimização para o código a seguir: a = b + c; x = a; d = x + e; Resposta correta. C. a = b + c; d = a + e; A variável x recebe o valor de a após a expressão a = b + c. No momento em que a expressão d = x + e é executada, o valor de x e a são os mesmos, podendo substituir x nessa expressão pelo próprio valor de a. Por fim, x = a pode ser eliminado por não ter mais serventia para o código. Assim, d = b + c + e; não pode ser utilizado porque o valor de a consiste em um valor diferente das demais variáveis e que pode ser utilizado em outra parte do código, o que não acontecia com x que terá sempre o mesmo valor de a. 5. As técnicas de otimização dependentes de máquina lidam diretamente com o código final, em que o código fonte já foi traduzido para um conjunto de instruções compreensíveis por ela. Dado o código a seguir, assinale a alternativa que apresenta uma simplificação válida utilizando a técnica de alocação e atribuição de registradores, considerando as variáveis x e y como necessárias no restante do código. Considere as seguintes instruções: LD: carrega um valor em um registrador ST: armazena um valor em uma variável DEC: decrementa um registrador ADD x, y, z: efetua uma soma: x = y + z SUB x, y, z: efetua uma subtração: x = y - z LD R0, 5 ADD R0, R0, b ST x, R0 LD R0, x SUB R0, R0, c ST y, R0 LD R0, y DEC R0 ST y, R0 2 4 5 3 - * + Resposta correta. A. LD R0, 5 ADD R0, R0, b ST x, R0 SUB R0, R0, c DEC R0 ST y, R0 Não há necessidade de utilizar um segundo registrador (R1). Se decrementar R0 sem armazenar antes o resultado de R0-c no próprio R0, o resultado será diferente do original. Ao aproveitar sempre o registrador R0, os armazenamentos e carregamentos de R0 novamente são desnecessários enquanto a conta prossegue. Ainda assim, o armazenamento (ST) de x e y, como descrito no código, é necessário, já que serão valores buscados no resto do programa. Logo, pode-se evitar o uso de R1e das instruções de carregamento (LD), mas não das instruções de armazenamento (ST). Assim, a alternativa correta é: LD R0, 5 ADD R0, R0, b ST x, R0 SUB R0, R0, c DEC R0 ST y, R0 1. A tarefa de otimização de código traz à tona diversas questões que devem ser levadas em consideração. Inúmeros fatores podem contribuir na complexidade das análises realizadas quanto ao fluxo de dados e de controle na otimização. Por isso, definir as melhores técnicas a serem utilizadas nem sempre é uma tarefa fácil. Além disso, sempre é preciso ficar atento ao objetivo final do programa quando se está realizando a otimização de código. Leia as afirmações a seguir sobre os cuidados que devem ser tomados no momento de realizar as otimizações nos códigos: I. Modificações que possam ter impacto prejudicial no código objeto devem ser evitadas, pois a intenção da otimização é melhorar o desempenho do código, conservando a essência do programa. II. As trocas de comandos (como realizar a transformação de while pelo if) nos códigos intermediários devem ser evitadas, pois podem prejudicar o significado dos códigos originais. III. O empenho aplicado para otimizar os códigos deve ser satisfatório e estimável, ou seja, algo que possa ser medido, garantindo otimização adequada e satisfatória. Assinale a alternativa que contém as assertivas corretas: Resposta correta. C. apenas I e III. As afirmações I e III estão corretas, pois tratam de questões pertinentes aos cuidados que devem ser tomados no momento de aplicar as otimizações, como preservar o significado do programa original e medir o quanto a otimização pode ser eficiente. Já a afirmação II não está correta, pois utiliza um exemplo que não se aplica na situação de transformações dos códigos intermediários, tendo em vista que a troca dos comandoswhile pelo ifpode ocorrer no momento da geração de códigos intermediários para realizar otimizações. 2. A otimização, levando em consideração a análise de fluxo de controle, é responsável pelo comportamento do código em relação aos laços e às repetições, algo crucial no momento de otimizar os códigos intermediários. As análises podem ser realizadas pelos blocos básicos de forma global ou local, e a representação da estrutura de um código pode ser realizada por meio de grafos de fluxo de controle. Uma das técnicas que podem ser aplicadas na análise de fluxo de controle é a _________________, que apresenta como objetivo realizar a transformação de variáveis que mudam constantemente a cada iteração. Esse tipo de variável pode ser encontrado nos acessos a vetores e matrizes. Leia as alternativas e assinale a que preenche a lacuna de forma correta: Resposta correta. B. simplificação de variáveis de indução. Quando são utilizadas propriedades algébricas na otimização, a redução de capacidade realiza substituições de operadores de alto custo por operadores mais simples. As variáveis de indução, da simplificação de variáveis de indução, mudam constantementeem cada iteração, pois estão presentes, geralmente, nos acessos a vetores e matrizes e, assim, podem ser transformadas. Assim, uma das técnicas que podem ser aplicadas na análise de fluxo de controle é a simplificação de variáveis de indução. Quando um bloco de instruções não tem a possibilidade de ser executado, é possível aplicar a eliminação de código morto. Expressões que estão presentes nos laços e não se modificam ao longo da execução podem ser analisadas pela técnica de movimentação de código. E a eliminação de desvios desnecessários trata de instruções de desvios que não são necessários, pois geralmente não são executados e, assim, podem ser eliminados. 3. Um bloco básico pode ser analisado de forma local ou global quando se está realizando otimização no código. A análise de fluxo de dados procura verificar variáveis e expressões presentes nos blocos básicos a fim de aplicar técnicas para melhorar o desempenho. Assinale a alternativa correta quanto à análise de fluxo de dados. Você acertou! D. A análise de variáveis diferentes, mas que têm valores iguais, pode ser realizada, tendo em vista que essas variáveis podem ser eliminadas posteriormente, contribuindo na otimização. A eliminação de expressões repetidas pode ser definida como a técnica de eliminação de código redundante, mas ela não pode ser eliminada de imediato, pois primeiramente a análise de fluxo de dados verifica se essa expressão não é utilizada posteriormente com outros valores atribuídos. A comunicação entre os blocos básicos é permitida pelas variáveis e expressões dos conjuntos de entrada (in) e saída (out) entre os blocos básicos, e não os predecessores (pred) e seus sucessores (succ). Os grafos acíclicos dirigidos (DAG) têm como foco analisar a sequência das instruções, facilitando a descoberta de subexpressões comuns, por exemplo. As expressões chamadas de available não precisam ser recalculadas, pois estão sempre disponíveis. A propagação de cópias é a técnica que verifica que variáveis diferentes são utilizadas com os mesmos valores e, assim, podem ser eliminadas posteriormente. Assim, a análise de variáveis diferentes, mas que têm valores iguais, pode ser realizada, tendo em vista que essas variáveis podem ser eliminadas posteriormente, contribuindo na otimização. 4. A análise de fluxo de controle e de dados na otimização de códigos intermediários pode ser feita de maneira conjunta para melhorar o desempenho, sempre visando a contribuir positivamente na geração do código, tendo como princípio a aplicação de diferentes técnicas para tal. Observe a expressão a seguir em linguagem C: y[i] = x[i] * x[i-1] Levando em consideração que todas as variáveis são do tipo inteiro, o bloco de instruções em código intermediário de três endereços ficaria desta forma: Linha 1 ➔ _t1 := 4 * i Linha 2 ➔ _t2 := i - 1 Linha3 ➔ _t3 := 4 * _t2 Linha 4 ➔ _t4 := 4 * i Linha 5 ➔ y[_t4] := x[_t1] * x[_t3] Assinale a alternativa correta que apresenta as técnicas que podem ser aplicadas para otimizar esse trecho de código. Você acertou! B. Identidades aritméticas e eliminação de subexpressões comuns. Nesse trecho de código, a técnica de movimentação de código não pode ser aplicada, pois não são expressões que estão presentes em laços. A eliminação de desvios desnecessários trata de instruções de desvios, não sendo adequada a esse trecho de código. A eliminação de código morto também não pode ser aplicada, tendo em vista que se refere a um bloco de instruções que não tem a possibilidade de ser executado. Entre as propriedades algébricas está a redução de capacidade, que troca operadores mais complexos por mais simples, mas, nesse caso, não há essa possibilidade. Para realizar a otimização do trecho de código, as técnicas que podem ser aplicadas são identidades aritméticas(linha 2) e eliminação de subexpressões comuns (linhas 1 e 4). Observe como fica o trecho de código intermediário otimizado: _t1 := 4 * i _t2 := _t1 - 1 y [_t1] := x[_t1] * x[_t2] 5. O grafo de fluxo de controle é uma ferramenta muito importante na análise de fluxo de controle, pois possibilita não só realizar uma representação gráfica da estrutura dos blocos básicos, mas também representa as dependências entre eles. A partir disso, a aplicação de técnicas pode ser facilitada nos códigos intermediários. Leia as afirmativas a seguir, que tratam da construção dos grafos de fluxo de controle, e classifique-as em verdadeiras (V) ou falsas (F). ( ) Cada bloco básico pode ser atribuído a uma aresta dirigida que representa as instruções de saltos para outros vértices. ( ) Saltos condicionais e incondicionais apresentam duas arestas (condições verdadeiras ou falsas), mas, quando não há saltos de instruções, é atribuída apenas uma aresta. ( ) Os vértices representam cada bloco básico, e as arestas, as instruções de saltos (podendo ser condicional ou incondicional). Assinale a alternativa que apresenta a ordem correta das afirmativas: Você acertou! C. F – F – V. Para a construção dos grafos de fluxo de controle, é necessário levar em consideração que os vértices representam cada bloco básico, e as arestas, as instruções de saltos (podendo ser condicional ou incondicional). Quando há saltos condicionais, duas arestas são lançadas (condições verdadeiras ou falsas), mas, se os saltos são incondicionais ou sem saltos, há apenas uma aresta. Os vértices que não têm arestas representam blocos com instruções de retorno de funções. 1. Gerir os recursos disponibilizados pela máquina é um processo que demanda habilidade tanto do desenvolvedor quanto da eficiência do compilador. A alocação de memória, qualquer que seja consiste em: Você acertou! E. atribuir espaço suficiente e o tipo de alocação mais adequado para cada variável. A reserva de espaço extra pode ocorrer em variáveis cujo tamanho é desconhecido, embora a maioria delas já tenha um tamanho definido. A alocação de memória pode lidar com o código a ser executado, mas para alocá-lo na memória RAM em tempo de execução, enquanto a posição em um disco rígido é atribuição do sistema operacional. A seleção de registradores diz respeito ao processo de otimização do código. A eliminação de objetos não mais referenciados faz parte do processo de coleta de lixo na memória heap, mas que não está necessariamente presente, tampouco integrando outras formas de alocação. No entanto, para todos os casos, é necessário encontrar a quantidade de memória suficiente para armazenar um dado e se este poderá ou não ser alterado — não podendo ser estático —, além do tempo de vida para determinar entre as modalidades de alocação dinâmica. 2. As variáveis alocadas estaticamente não podem ter seus valores alterados. Assinale a alternativa que cita uma vantagem da alocação de memória do tipo estático. Resposta correta. A. Velocidade de acesso. Por serem fixas e permanentes, espera-se um maior uso de memória na alocação estática. A alocação não é feita em tempo de execução, e sim durante a compilação. Os endereços, uma vez definidos, não serão alterados, tampouco o tamanho da memória alocada. A velocidade de acesso é maior, já que os endereços são fixos e previamente conhecidos, constituindo esta uma vantagem. 3. Na alocação de memória dinâmica, o tamanho e o endereço a ser utilizado somente serão conhecidos pelo sistema no momento da execução. Assinale a alternativa que apresenta uma desvantagem desse tipo de alocação. Resposta correta. A. Requer mecanismo de limpeza. A alocação dinâmica pode ocorrer de duas formas: pilha ou heap. Os dados são alocados em tempo de execução, o que torna a compilação mais rápida. É possível aumentar o tamanho de uma variável utilizando a alocação heap. Uma vez que muitas variáveis podem ser criadas em tempo de execução, é necessário implementar um sistema de limpeza, a fim de liberar espaço sempre que variáveis anteriormente alocadas se tornem inúteis, sendo esta uma desvantagem. 4. A alocação de memória dinâmica pode ser feita em modo pilha (stack) e em modo heap (monte). Apesar de não existir uma regra única, existem situações em que determinado tipo é mais benéfico que o outro. A pilha é a principal implementação quando se dá a alocação em virtude da(do): Você acertou! C. chamada de uma função. Valores constantes podem ser armazenados na memória estática, liberando espaço na memória dinâmica para outras variáveis, além de aproveitarem a maior velocidade de acesso nesse caso. Dentro da pilha, não é possível alterar o tamanho de uma variável em virtude do mecanismo de LIFO. Variáveis globais são utilizadas em diferentes escopos e não podem ser eliminadas, como no sistema LIFO. O carregamento do programa na memória durante a inicialização faz uso da memória estática, uma vez que é mais rápida e o programa não pretende ser alterado em tempo de execução. Já durante a chamada de uma função, espera-se que variáveis necessárias somente dentro daquele escopo sejam utilizadas, como os argumentos e dados de retorno, que poderão ser eliminados da memória tão logo a função retorne. 5. Normalmente, a pilha apresenta um tamanho previamente definido e que, se ultrapassado, resultará em uma exceção conhecida como stack overflow (ou “estouro de pilha”). Qual das alternativas a seguir apresenta uma forma de reduzir as ocorrências de stack overflow? Você acertou! A. Manter somente a referência ao objeto na stack. Ao fazer uso de argumentos de tamanho flexível, caso o sistema tente usar a pilha para essa finalidade, pode ser alocado um espaço menor que o necessário e, quando a variável aumentar, ocorra a sobreposição de dados, podendo inclusive superar o próprio tamanho da pilha. Os argumentos das funções costumam ser armazenados em uma pilha junto a outros valores, como os dados temporários, os endereços de referência para retorno, os valores de retorno, entre outros. Assim, a quantidade e o tamanho desses argumentos impactam diretamente no tamanho da pilha. Chamadas consecutivas, sem retorno, seja por instrução recursiva, seja por aninhamento de múltiplas funções, podem fazer com que a pilha precise armazenar mais dados que o comportado. Para cada chamada, um novo conjunto de dados é armazenado, o qual somente será eliminado ao retornar da função. Assim, variáveis muito grandes devem ser passadas como argumento, e a pilha pode ser utilizada para armazenar apenas um ponteiro para essa variável e alocar os dados em si na memória heap. Portanto, uma maneira de reduzir as ocorrências de stack overflow consiste em manter somente a referência ao objeto na stack. UNIT 4 1. Basicamente, há duas fases de compiladores, e cada uma delas cria uma representação do código original mais próxima do código que a máquina consegue executar. Considerando o momento em que a síntese é realizada e seus objetivos, assinale a alternativa correta. Resposta correta. B. A síntese é realizada após os
Compartilhar