Exercícios — Análise Sintática
Os exercícios assinalados com "✓" serão resolvidos nas aulas práticas; Os assinalados com "†" têm elevada dificuldade. Todos os restantes devem ser resolvidos pelos alunos.
- Exercícios — Análise Sintática
Limpeza de uma Gramática
Exercício 01
Considere a gramática independente do contexto com produções
- Escreva uma expressão regular que represente a linguagem gerada por .
- Construa uma gramática (essencialmente) não contraível equivalente.
- Elimine as produções unitárias da gramática obtida na alínea anterior.
Exercício 02
Repita o exercício anterior para a gramática:
- 
com produções 
- 
com produções 
Exercício 03
Construa uma gramática equivalente a , com produções que não contenha produções unitárias e escreva uma expressão regular que represente a linguagem gerada por esta gramática.
Exercício 04
Construa uma gramática equivalente a , com produções sem símbolos inúteis e encontre uma expressão regular que represente a linguagem gerada por esta gramática.
Exercício 05
Construa uma gramática equivalente a , com produções na forma normal de Chomsky.
Exercício 06
✓ Construa uma gramática equivalente a , com produções na forma normal de Greibach.
Exercício 07
Repita o exercício anterior para a gramática
- Com produções
- Com produções
Gramáticas LL(k)
Exercício 08
Calcule os símbolos diretores e determine se as seguintes gramáticas são :
- ✓
Exercício 09
†✓ Modifique a gramática de modo a obter uma gramática equivalente. As produções de são: Sugestão: Comece por determinar .
Exercício 10
Repita o exercício anterior para a gramática
Exercício 11
Defina uma gramática que gere a linguagem das expressões aritméticas com subtração, multiplicação e parêntesis. (Use o símbolo para representar os inteiros.)
Gramáticas LR(0)
Exercício 12
Determine os da gramática com produções
- ✓
Exercício 13
✓ Considere a gramática .
- Construa o autómato dos itens válidos de .
- Diga, justificando, se é .
- Construa a tabela de análise sintática para .
- Defina o autómato de pilha reconhecedor para .
Exercício 14
Repita as duas primeiras alíneas do exercício anterior para a gramática Se concluir que a gramática não é , enumere os conflitos encontrados.
Exercício 15
Repita o exercício anterior para a gramática
Gramáticas LR(1)
Exercício 16
✓ Considere a gramática , com o conjunto de produções:
- Construa o autómato dos itens válidos de .
- Diga, justificando, se é .
- Construa o autómato amalgamado e diga, justificando, se é .
- Construa a tabela de análise sintática para .
- Com base no autómato dos itens válidos de , defina o autómato de pilha reconhecedor .
- Construa a computação do autómato de pilha para a palavra .
Resolução em Banda Desenhada
| AIV | TAS | APR | 
|---|---|---|
|  |  |  | 
Exercício 17
✓ Repita o exercício anterior com a gramática , com o conjunto de produções:
Exercício 18
✓ Repita as primeiras 4 alíneas do exercício anterior com a gramática , com o conjunto de produções: (Se não é , não faça as alíneas que não se aplicam.)
Exercício 19
Repita o exercício anterior com as gramáticas
- , com o conjunto de produções:
- , com o conjunto de produções:
- , com o conjunto de produções:
Implementação
As indicações gerais para os exercícios de implementação são válidas aqui.
Uma biblioteca para Grafos Orientados, com Pesquisas
Para este grupo de exercícios pode usar, entre outros, os grafos indicados a seguir.
# Grafos para testar as implementações.
simples = [
  (0, 2),
  (1, 3),
  (2, 0),
  (3, 1),
]
cubo = [
  (0, 1), (0, 4),
  (1, 2), (1, 5),
  (2, 3), (2, 6),
  (3, 0), (3, 7),
  (4, 5), (5, 6), (6, 7), (7, 4)
]
cubo_inclinado = [
  (0, 1), (0, 3), (0, 4),
  (1, 2), (1, 5),
  (2, 6),
  (3, 7),
  (4, 5), (4, 7),
  (5, 6),
  (7, 6)
]
- Represente. Uma aresta orientada  por um tuplo (a, b).
- Represente. Um Grafo (orientado) por uma lista de arestas.
- Implemente. A função nodes(graph)que devolve o conjunto do vértices do grafo.
- Implemente. A função children(graph, node)que devolve o conjunto dos filhos do vérticenode.
- Implemente. A função parents(graph, node)que devolve o conjunto dos pais do vérticenode.
- Implemente. A função is_path(graph, path)que testa sepathé um caminho no grafograph. Um caminho é uma lista de vértices em que dois consecutivos estão ligados por uma aresta.
- Implemente. A função has_loop(graph, path)que testa sepathtem algum ciclo no grafograph. Um ciclo é um caminho que começa e termina no mesmo vértice.
- Implemente. A função search(graph, a, b)de acordo com os apontamentos teóricos. Considere uma variante em que as funçõesexpandejoinsão passadas como argumentos opcionais. Verifique o que acontece se não controlar os vértices visitados.
- Teste. Pesquisas ascendentes, descendentes, em largura e em profundidade.
Uma biblioteca para Árvores, com Pesquisas
- Represente. Um nó/vértice é representado pela estrutura Nodecom atributos:- value: Qualquer "coisa" que pode estar no nó.- Obs. nem todas as linguagens permitem isto facilmente. Em pythonekotlin, por exemplo, é direto.
 
- Obs. nem todas as linguagens permitem isto facilmente. Em 
- children: Uma lista de- Node.- Obs. esta recursividade também levanta problemas em algumas linguagens. Por exemplo, em rustterá de fazer algo do géneroVec<Box<Node>>. Empythonnão terá qualquer dificuldade.
 
- Obs. esta recursividade também levanta problemas em algumas linguagens. Por exemplo, em 
 
- Implemente. A função leaf(value)cria uma folha (isto é, um nó sem filhos).
- Implemente. A função binop(op, lhs, rhs)cria um nó com valueope dois filhos:lhserhs. Considere variantes em quelhs, rhssãoNodeou são um tipo qualquer.
- Implemente. A função node(value, children)aceita um número variável de valores emchildren. Por exemplonode("plus", 3, 4, 5)e nãonode("plus", [3, 4, 5]).
- Escrita. A função repr(node)devolve umastringcom a representação de uma árvore, um valor por linha. Indente os descendentes. Por exemplo:
plus
- 3
- times
  - 4
  - 5
Alternativamente, as árvores podem ser representadas por listas de listas. Por exemplo:
("plus" 3 ("times" 4 5)) =
("plus"
    3
    ("times"
        4
        5
))
Desta forma, em geral n = Node(value, children) define a lista [n.value] + CHILDREN em que CHILDREN é a lista das representações dos nós em n.children.
N.B. usando a notação do python, tree.children = tree[1:] e  tree.value= tree[0]. Além disso, a representação das folhas é o próprio valor, não uma lista.
Pesquisa:
- Implemente. A função searchpara pesquisas (em profundidade e em largura).- Começe com pesquisas por valor exato: Por exemplo, search(tree, value)procura emtreeum nóntal quen.value == value. No caso de existir um nó nessas condições, devolve o caminho para esse nó.
 
- Começe com pesquisas por valor exato: Por exemplo, 
Caminhos para nós descendentes. Por exemplo, se tree for o exemplo acima, o caminho para chegar ao nó com o valor 4 é a lista [1, 0] porque começando na raiz (o nó com valor plus) escolhe-se o filho na posição 1 (o nó com valor times  e filhos [4, 5]) e, desse nó escolheu-se o filho na posição  0; Isto é, o caminho [0, 1] indicas os indices dos filhos escolhidos, quando se "desce" a árvore.
O caminho para chegar ao valor 5 seria [1, 1] e para chegar a times seria apenas [1]. Finalmente [0] dá o valor 3 e o caminho []  leva ao valor ... (exercício).
- 
Implemente. A função subtree(tree, path)devolve o nó que corresponde ao caminhopath. Se não existe tal nó, devolve "não definido".
- 
Generalize. Há várias generalizações interessantes: - Use um predicado em vez de um valor. Por exemplo, para encontrar um nó com o valor par: tree.searchD(lamba x: x.value % 2 == 0).
- Encontre todas as ocorrências em vez de apenas uma. Ou encontre até nocorrências. Por exemplo:tree.search(lambda x: len(x.children) > 2, n=42)para encontrar 42 nós com mais do que dois filhos. Convencione que, senfor "não definido" procura todas as ocorrências.- A pesquisa de todas as soluções também tem um tratamento interessante que consiste em implementar a pesquisa como uma stream que produz um novo nó sempre que solicitada. No caso do pythonpode usaryield. Noutras linguagens, ymmv.
 
- A pesquisa de todas as soluções também tem um tratamento interessante que consiste em implementar a pesquisa como uma stream que produz um novo nó sempre que solicitada. No caso do 
- Implemente outras estratégias de pesquisa. Por exemplo a Iterative deepening depth-first, é uma forma melhorada de pesquisas em profundidade.
 
- Use um predicado em vez de um valor. Por exemplo, para encontrar um nó com o valor par: 
Analisadores LL(1)
- Considere a GIC
em que os terminais são e .
Verifique (manualmente) se é LL(1) e, nesse caso, implemente um analisador sintático LL(1). A função
parse(s)tem como argumento a stringse devolve uma árvore.
- Comece por separar (tokenize) a palavra numa lista de símbolos.
- Precisa de um "contexto" de processamento com:
- O símbolo seguinte: next.
- A lista dos símbolos que faltam processar.
- A função consume(symbol).
 
- O símbolo seguinte: 
- Processe a lista de tokens usando as funções proc_Expr(token, tokens, tree)eproc_Seq(token, tokens, tree):- tokené o símbolo de avanço.
- tokenssão os restantes símbolos da palavras (incluindo- token).
- treeé a árvore que resulta do processamento da função.
 
- Vai precisar de funções auxiliares como consume(token, tokens)
Uma biblioteca para GIC LL(1)
Se resolver sozinho estas alíneas ganha o direito de se gabar nas aulas de ALP durante uma semana. E o dever de ajudar os seus colegas.
- ‡ Implemente. O método
firsts()que devolve um mapa do tiposymbol: set<symbol>que associa a cada símbolo (variável ou terminal) da gramática o conjunto dos seu primeiros.- ‡ Implemente. O método
follow()que devolve um mapa do tiposymbol: set<symbol>que associa a cada símbolo (variável ou terminal) da gramática o conjunto dos seu seguintes.
Análise Sintática LL(1)
- Implemente. O método firsts_of(symbol)devolve o conjunto dos primeiros desymbol.
- Implemente. O método follow_of(symbol)devolve o conjunto dos seguintes desymbol.
- Implemente. O método directors(rule)devolve o conjunto de diretores da regrarule.
- Teste. O método is_LL1()testa segrammaré LL(1).
Uma biblioteca para GIC LR
Itens
- Represente. Um item LR(1) é representado pela classe Itemcom atributos:- ruleuma regra.
- lookaheaduma lista de símbolos.
- positionum inteiro, que indica a posição do "ponto".
 
- Teste. O método is_complete()testa se o item é completo.
- Implemente. O método active_prefix()devolve o prefixo até à posição do "ponto".
- Implemente. O método active_symbol()devolve o símbolo imediatamente a seguir ao ponto se o item não for completo e "não definido" caso contrário.
- Implemente. O método active_suffix()devolve o sufixo desde a posição a seguir ao "ponto".- N.B: active_prefix + active_symbol + active_suffixdeve reconstruir o lado direito da produção.
 
- N.B: 
- Implemente. O método transition(symbol)devolve o item que resulta de "ler"symbol. Isto é, sesymbol == active_symbolno novo item o "ponto" avança uma posição. Caso contrário devolve "não definido".
- Escrita. O método __repr__()devolve umastringda formaC〈V → P · A S ⎅ L〉em que:- Cé "- '★'" quando o item é completo e vazia (- '') caso contrário.
- Vé o lado esquerdo da produção.
- P,- Ae- Ssão o prefixo, símbolo e sufixo ativos.
- Lé a lista dos símbolos de avanço, separados por espaços. Se for vazia, use- ∅.
- Use mesmo os símbolos indicados: ★〈→·⎅∅〉
 
Análise Sintática, Autómato dos Itens Válidos
Na biblioteca para os AFD estes são construídos com uma lista de transições, o estado inicial e uma lista de estados finais. Além disso, os estados são inteiros.
Mas, na construção do Autómato dos Itens Válidos os estados "contêm" conjuntos de itens. Esta diferença é resolvida mantendo uma associação (mapa, dicionário) dos estados do AFD para os conjuntos de itens. A vantagem é que não é preciso mudar a implementação dos AFD. A desvantagem é que se torna necessário fazer alguma "manutenção" dessa associação.
- 
Representação. Uma classe VIAcom atributos:- grammara gramática.
- fsao autómato dos itens válidos calculado para- grammar.
- statesa associação dos estados do autómato para os conjuntos de itens.
- tablea tabela de análise sintática, uma associação dos estados+avanços do autómato para reduções/transferências.
- phaseum inteiro (ou enum) para marcar a fase de processamento:- INITIAL = 0→- GRAMMAR_OK→- START_OK→- VIA_OK→- TABLE_OK. Se algum destes passos correr mal →- ERROR.
 
- 
Construção. O método make_VIA(). Segrammar.is_valid()incrementaphase.
- 
† Implemente. O método calc_startCore()que calcula o núcleo do estado inicial devia. Devolve um conjunto de itens.
- 
† Implemente. O método close(itens)que calcula o fecho de um conjunto de itens. Devolve um conjunto de itens.
- 
Implemente. O método build_START()que calcula o estado inicial devia. Se correr bem incrementaphase.
Se resolver sozinho todas as alíneas seguintes ganha o dever de ajudar os seus colegas. E o direito de se gabar nas aulas de ALP durante uma semana.
- † Implemente. O método build_FSA()processagrammarpara definir o autómatofsa, a associaçãostateseis_built. Se correr bem incrementaphase.
- † Implemente. O método build_TABLE()processagrammar,fsaestatespara definirtable. Se correr bem incrementaphase.
- † Implemente. O método derive(word)para encontrar uma derivação dewordpela gramáticavia.grammar. N.B. não é necessário implementar autómatos de pilha.
- † Implemente. O método derivation_tree(word)que calcula a Árvore de Derivação deword.
- Controlo. Use phasepara evitar computações repetidas, precoces ou desnecessárias. Por exemplo, suponha que houve um erro a calcular o estado inicial. Então quando "chama"build_FSAdeve evitar calcular o AFD. Por outro lado, se já calculoufsacom sucesso e "acidentalmente" chamabuild_FSAnão há razão para repetir esses cálculos.- Também pode usar phasepara implementar cálculosbuild---"inteligentes" (Ah! Ah!). Suponha que "chama"build_TABLEmasphase < VIA_OK. Nesse ponto pode chamarbuild_VIAque por sua vez faz uma análise semelhante.
 
- Também pode usar