Panorama da Compilação e Linguagens Formais

O mapa que o percurso inteiro vai usar para se localizar

Autor
Afiliações

Moacyr Francischetti Corrêa, Bacharel em Ciência da Computação, Licenciado em Computação, Especialista em Ciência de Dados e Inteligência Artificial, PhD em Biotecnologia in Silico

Roteiro da aula

  1. Três programas defeituosos
  2. O contrato de correção
  3. Compilar, interpretar e os parentes
  4. As fases pelas interfaces
  5. O mapa teórico das gramáticas
  6. O que nenhum compilador pode fazer · demonstração

Ao final, você será capaz de

  • distinguir compilador, interpretador, montador e tradutor entre linguagens de alto nível;
  • nomear as fases e dizer o que cada uma recebe e produz;
  • localizar as quatro classes da hierarquia e a máquina de cada uma.

Bloco de abertura. Não anuncie o roteiro antes dos três programas: chegue com eles já projetados e sem título. Este slide entra depois, para nomear o que a turma acabou de tentar explicar. Escrever o roteiro no quadro e deixá-lo visível nas duas aulas.

Anunciar que a segunda aula termina com código digitado ao vivo — quem só assiste não aprende aquele bloco.

Três programas defeituosos

Por que o compilador aceita o terceiro?

Sem ponto e vírgula

Reclamação imediata, apontando linha e coluna.

Texto somado a número

Reclamação mais tarde, apontando a operação inteira.

Laço que nunca termina

Nenhuma reclamação. O executável é gerado.

Forma · sentido · indecidibilidade — as três palavras ficam no canto do quadro até o fim da segunda aula.

Projete os três programas lado a lado, sem título e sem explicação, e faça só esta pergunta. Deixe a sala responder em voz alta e anote as hipóteses no quadro sem julgá-las.

A hipótese que quase sempre aparece é “porque o terceiro é mais difícil de detectar”. É o gancho: responda que não é difícil, é impossível, que existe demonstração desde 1936 — e não diga mais nada até o bloco de indecidibilidade da segunda aula. A pendência declarada segura a atenção melhor do que a resposta imediata.

Feche escrevendo as três palavras no canto do quadro e não as apague.

O contrato de correção

Traduzir é preservar o significado

\llbracket T(p) \rrbracket_{L_a}(x) = \llbracket p \rrbracket_{L_f}(x)

T é uma função parcial: nem todo texto é programa, e o tradutor tem obrigação de dizer isso em vez de produzir código sem sentido.

Escreva a igualdade no quadro antes de projetar. Dois pontos merecem verbalização explícita, porque a turma passa por eles sem notar: a parcialidade, e o plural dos diagnósticos — quem reporta só o primeiro problema é insuportável de usar. Essa observação prosaica é o que justifica, mais adiante, a infraestrutura de erro que os grupos vão construir.

Pergunta que rende: um compilador que aceitasse todo texto e sempre produzisse algum executável seria melhor ou pior?

Otimizar é trocar o programa mantendo a igualdade

O que é otimização

Substituir um programa por outro mais rápido com a igualdade intacta em todas as entradas.

O que não é

Uma transformação que muda o resultado num único caso de fronteira. Isso não é otimização agressiva: é defeito.

Amarrar a igualdade à otimização já aqui é o que a torna memorável — sem isso ela vira uma linha decorada. Adiante, quando o otimizador entrar em cena, esta é a frase que o restringe.

Se alguém perguntar por que não se busca o programa ótimo, responda em uma frase — encontrá-lo é indecidível — e volte ao roteiro; o assunto tem bloco próprio na segunda aula.

Compilar, interpretar e os parentes

A diferença cabe numa palavra: quando

Compilar

A análise é paga uma vez e amortizada. Em troca, o código gerado precisa servir a todos os valores possíveis.

Interpretar

A análise é paga a cada execução, às vezes a cada iteração. Em troca, sabe-se o valor de cada variável.

Conduza pela pergunta “quando”, nunca por uma lista de diferenças. Compilador e interpretador fazem a mesma análise; o que muda é o momento em que ela é paga. Do “quando” as duas consequências saem sozinhas, sem decorar.

Aqui aparece quase sempre a digressão “por que a linguagem X é mais rápida que a Y”. É legítima: responda em duas frases, reconduzindo ao custo da análise, e proponha que a comparação concreta volte quando houver vocabulário de otimização para sustentá-la.

Os parentes, em duas frases

Montador

Formalmente um compilador cuja fonte já corresponde um a um às instruções da máquina. O nome separado existe porque nenhuma dificuldade interessante sobra.

Tradutor entre linguagens de alto nível

Precisa preservar estrutura, nomes e formatação — o que um compilador para código de máquina pode dissolver sem culpa.

Seja econômico neste slide: ele existe para fechar o vocabulário, não para render discussão. O que interessa é a razão de o montador ter nome próprio — não há instrução a escolher nem registrador a alocar, porque quem escreveu o fonte já fez as duas coisas.

Dois eixos independentes, não duas gavetas

flowchart TB
    subgraph distancia["Eixo 1 — distância semântica entre fonte e alvo"]
        direction LR
        M["montador<br/>correspondência quase um para um"] --> T["tradutor entre linguagens<br/>de alto nível"] --> C["compilador para<br/>código de máquina"]
    end

    subgraph tempo["Eixo 2 — quando a execução acontece"]
        direction LR
        AN["análise antes,<br/>execução depois"] --> CO["compilação"]
        AF["análise e execução<br/>no mesmo momento"] --> IN["interpretação"]
    end

    distancia --> HIB["sistemas híbridos<br/>compilam para máquina virtual<br/>e interpretam o resultado"]
    tempo --> HIB

Não peça que decorem fronteiras. O ponto do diagrama é que as duas dimensões são independentes, e por isso um sistema pode ocupar posição nas duas ao mesmo tempo.

Deixe o diagrama no ar ao passar para a votação: quem votar por eliminação vai usá-lo.

Primeira votação

Traduz para uma máquina virtual e compila em execução os trechos frequentes. Esse sistema é:

    1. um compilador;
    1. um interpretador;
    1. os dois, porque as definições falam de objetos diferentes;
    1. nenhum dos dois, por ser híbrido sem classificação.

Procedimento inteiro: voto individual primeiro, sem nenhum comentário seu; discussão em duplas; segundo voto. Revelar a resposta antes da discussão anula a técnica.

Resposta: (c). Quem vota (a) ou (b) tenta encaixar o sistema numa gaveta única. Quem vota (d) percebeu o problema da gaveta e concluiu que não há classificação — é o erro mais interessante, e é dele que se fecha o conceito: a pergunta produtiva não é “isso é compilador ou interpretador”, e sim “o que foi decidido antes da execução e o que foi adiado”.

Votação anônima na projeção. O valor do primeiro voto está na dispersão.

As fases pelas interfaces

Fase não é nome: é uma tripla

Recebe

A representação que a fase anterior produziu.

Produz

A representação de que a fase seguinte precisa.

Detecta

Os erros que só ela tem condições de perceber.

Em dúvida sobre onde colocar uma verificação: qual é a primeira fase que dispõe da informação necessária?

Este é o erro de condução mais comum do módulo: fazer a turma decorar seis nomes. A terceira componente da tripla é a que sobrevive ao percurso inteiro, e o critério da nota é o que se leva para casa.

Construa a tabela do próximo slide no quadro, uma linha por vez, perguntando antes de escrever. A tabela projetada é conferência, não exposição.

Do texto ao código

flowchart LR
    A["texto-fonte<br/>sequência de caracteres"] --> B["análise léxica"]
    B --> C["sequência de tokens"]
    C --> D["análise sintática"]
    D --> E["árvore sintática"]
    E --> F["análise semântica"]
    F --> G["árvore anotada<br/>e tabela de símbolos"]
    G --> H["geração intermediária"]
    H --> I["representação intermediária"]
    I --> J["otimização"]
    J --> I
    I --> K["geração de código"]
    K --> L["código da máquina-alvo"]

    subgraph analise["Análise (frente)"]
        B
        C
        D
        E
        F
        G
    end

    subgraph sintese["Síntese (retaguarda)"]
        H
        I
        J
        K
    end

Percorra o diagrama pelas setas, nomeando o que trafega em cada uma — é o que impede a leitura como lista de caixas.

Chame a atenção para o laço da otimização sobre a representação intermediária: é a única fase cuja entrada e saída têm o mesmo formato, e isso não é detalhe de desenho.

As interfaces, linha a linha

Fase Recebe Produz Detecta
Léxica caracteres tokens símbolo desconhecido
Sintática tokens árvore sintática estrutura inválida
Semântica árvore árvore anotada e tabela de símbolos nome não declarado
Geração intermediária árvore anotada representação intermediária
Otimização representação intermediária representação intermediária
Geração de código representação intermediária código da máquina-alvo recurso insuficiente

Confira contra o que a sala ditou no quadro. As duas linhas com travessão costumam incomodar, e o incômodo é produtivo: fase que não detecta erro nenhum ainda é fase, porque a definição é pela interface.

Distinga aqui fase de passagem, em uma frase: o léxico costuma entregar um token por vez sob demanda do sintático, o que dá duas fases numa travessia só.

Por que existe uma representação intermediária

12 4 origens x 3 destinos, sem camada intermediária

7 4 + 3 peças, com a camada no meio

100 10 x 10, sem a camada

20 10 + 10, com a camada

O ganho maior é de manutenção: um defeito na análise de uma linguagem se corrige num lugar só e beneficia todas as máquinas.

Faça a conta na frente deles, no quadro, antes de projetar os números — o argumento pede aritmética, não retórica. Amplie de quatro por três para dez por dez e deixe a sala ver a diferença crescer.

Complete com o argumento que vale mais e é menos citado, o da nota. Ele é o que explica por que uma linguagem nova nasce já com bom código para dezenas de arquiteturas.

A assimetria que quase nunca é dita

Análise

Reconhecimento. Teoria madura, algoritmos ótimos, ferramentas que geram código a partir de especificação declarativa.

Síntese

Otimização combinatória. Subproblemas difíceis no caso geral, heurísticas onde não há ótimo alcançável.

Avise que o tom da segunda metade do percurso muda por causa disso, e que a mudança é do objeto, não de quem conduz. Estudantes que não ouvem esse aviso interpretam a mudança como falha didática.

Segunda votação

Programa bem pontuado usa, numa expressão, um nome nunca declarado. Qual é a primeira fase capaz de detectar isso?

    1. análise léxica;
    1. análise sintática;
    1. análise semântica;
    1. geração de código.

Resposta: (c). Quem vota (a) supõe que o analisador léxico conhece os nomes do programa — confunde reconhecer a categoria de um lexema com saber o que ele denota. Quem vota (b) confunde “estar de acordo com as regras da linguagem” com “derivar da gramática”: a sequência de tokens deriva perfeitamente, e é por isso que a fase seguinte existe.

O erro (b) costuma ser majoritário no primeiro voto e cair muito no segundo. Comente esse movimento com a turma quando o resultado aparecer: é o melhor argumento a favor da própria técnica.

Turma que converge para a alternativa errada precisa que você refaça a definição antes da discussão em duplas — a discussão só corrige quando há quem tenha entendido.

O mapa teórico

Retomada: qual das três palavras ainda não foi tratada?

Forma

Tratada: análise sintática.

Sentido

Tratada: análise semântica.

Indecidibilidade

Pendente.

Antes da impossibilidade vem o mapa em que ela mora.

Abertura da segunda aula. Não recapitule em forma de resumo: retome pelas três palavras que ficaram no canto do quadro e faça a pergunta. A turma responde sozinha.

Faça a inversão de ordem explicitamente, em voz alta: “para explicar por que aquilo é impossível, preciso primeiro mostrar o mapa em que a impossibilidade mora”. Sem isso, o bloco do mapa parece digressão.

Uma linguagem é qualquer subconjunto de \Sigma^*

L \subseteq \Sigma^*

Os programas corretos

Formam uma linguagem nesse sentido.

Os programas que terminam

Também formam. A diferença está na dificuldade de decidir a pertinência.

Passe rápido pelos objetos elementares — alfabeto finito e não vazio, cadeia como sequência finita, \varepsilon de comprimento zero, \Sigma^* infinito enumerável — e pare aqui, porque a generalidade da definição é o que a turma não vê sozinha.

Os dois cartões são o pré-anúncio da indecidibilidade: o mesmo aparato descreve os dois conjuntos, e um deles não é decidível. Não abra o assunto agora.

Duas gramáticas quase iguais

S \to a\,S\,b \mid \varepsilon

S \Rightarrow aSb \Rightarrow aaSbb \Rightarrow aabb

Gera \{a^n b^n \mid n \ge 0\}: é preciso contar.

A \to a\,A \mid b

A \Rightarrow aA \Rightarrow aaA \Rightarrow aab

Gera a^n b: não há nada a lembrar.

Derive as duas no quadro, passo a passo, antes de projetar. Faça a turma notar que, na primeira, a única produção que introduz um a também introduz um b — é daí que sai a conclusão sobre a linguagem gerada, e não de afirmação sua.

Pergunte o que mudou. A resposta que você quer ouvir é exatamente a das duas últimas linhas dos cartões.

Alguém sempre pergunta como se demonstra que a^n b^n não é regular. Responda que a demonstração vem adiante, dê a frase intuitiva e siga: ceder aqui consome o bloco de indecidibilidade.

Quatro andares

flowchart TB
    subgraph T0["Tipo 0 — irrestritas · máquina de Turing"]
        subgraph T1["Tipo 1 — sensíveis ao contexto · autômato linearmente limitado"]
            subgraph T2["Tipo 2 — livres de contexto · autômato de pilha"]
                subgraph T3["Tipo 3 — regulares · autômato finito"]
                    LEX["análise léxica<br/>categorias de tokens"]
                end
                SIN["análise sintática<br/>estrutura aninhada"]
            end
            SEM["condições dependentes de contexto<br/>verificadas por código, não por gramática"]
        end
        IND["território indecidível<br/>terminação e propriedades do comportamento"]
    end

Apresente a ideia de Chomsky pela restrição progressiva da forma das produções, nunca pela lista dos quatro tipos: a lista é o que a turma copia e não usa.

Registre a inclusão própria no quadro: \mathcal{L}_3 \subsetneq \mathcal{L}_2 \subsetneq \mathcal{L}_1 \subsetneq \mathcal{L}_0. Em cada nível existe linguagem que a classe abaixo não alcança.

A hierarquia lida como escala de memória

Tipo Gramática Reconhecedor Memória
3 regular autômato finito nenhuma além do estado atual
2 livre de contexto autômato de pilha ilimitada, com disciplina de topo
1 sensível ao contexto autômato linearmente limitado acesso livre, limitada pela entrada
0 irrestrita máquina de Turing acesso livre, ilimitada

É esta coluna, e não os números dos tipos, que explica por que existe um segundo modelo de máquina adiante no percurso.

Este é o slide que o percurso inteiro vai cobrar. Estudante que sai daqui sem a escala de memória chega ao bloco dos autômatos de pilha sem entender por que ele existe.

Amarre à coluna de memória o que já foi visto: casar delimitadores aninhados é exatamente ler e escrever no topo, e por isso a estrutura de blocos e expressões mora no segundo andar.

Dois avisos antes de seguir

Não determinismo

Para autômatos finitos não aumenta o poder de reconhecimento. Para autômatos de pilha, aumenta.

O mapa é elegante demais

Classifica por poder de descrição, não por custo de reconhecimento — e não captura ambiguidade.

O primeiro aviso é a origem de metade das dificuldades da análise sintática: o compilador precisa de um analisador determinístico e, portanto, não trabalha com todas as linguagens livres de contexto.

O segundo evita que a turma saia achando que a hierarquia responde às perguntas de engenharia. Ambiguidade é propriedade da gramática, não da linguagem, e para um compilador é intolerável.

Terceira votação

Sobre o alfabeto \{a, b\}, qual linguagem não é regular?

    1. cadeias com número par de a;
    1. cadeias da forma a^n b^n;
    1. cadeias que começam por a e terminam por b;
    1. cadeias que contêm abba como subcadeia.

Resposta: (b). Quem vota (a) supõe que contar é sempre proibido a uma máquina de estados finitos — mas contar módulo dois exige apenas dois estados, e desfazer essa confusão é o principal ganho da questão. Quem vota (d) acha que reconhecer uma subcadeia exige memória do que passou, quando basta lembrar quanto do padrão já foi casado.

Feche com a frase que os módulos seguintes vão formalizar: reconhecer a^n b^n exige lembrar um número ilimitado, e nenhum conjunto fixado de estados guarda um número ilimitado.

O que nenhum compilador pode fazer

A dívida da abertura, paga

Turing, 1936

Não existe procedimento mecânico que decida corretamente, para todo programa e toda entrada, se a execução termina.

O argumento é de autorreferência: supondo o procedimento, constrói-se com ele um programa que termina exatamente quando o procedimento afirma que não termina.

Volte ao terceiro programa da abertura antes de projetar. Esboce o argumento no quadro em três linhas e não vá além: a demonstração completa não é conteúdo desta disciplina, e forçá-la aqui consome o bloco seguinte, que é o de digitar código.

Diga que a impossibilidade é lógica, não tecnológica. É a confusão que sobra quando o esboço é curto demais.

Forma é decidível; comportamento não

Perguntas sobre a forma do texto

Decidíveis. São todas as que o compilador responde.

Perguntas sobre o comportamento

Indecidíveis, e não por limitação de engenharia.

A saída intermediária

Responder “certamente não” ou “talvez sim”, errando sempre para o mesmo lado.

Escreva o critério prático no quadro: ele é o que sobrevive ao percurso. Antes de tentar fazer o compilador detectar alguma coisa, pergunte se ela é propriedade do texto ou do comportamento.

O terceiro cartão é a ponte para a análise de fluxo, adiante. Anuncie-o como pendência, sem abrir.

Demonstração

O que se escreve quando nenhuma fase existe ainda

A decisão contraintuitiva

Não se começa pelo analisador léxico, e sim pela camada que todas as fases usam.

As duas peças

Saber onde as coisas estão no arquivo de entrada, e saber reclamar de forma útil quando algo dá errado.

Quem enfia o tratamento de posição dentro do analisador léxico está criando dívida garantida.

Bloco de construção ao vivo, e ele não é demonstração: avise a sala para abrir o editor e digitar junto. Pause ao fim de cada peça e espere as duplas alcançarem o mesmo ponto — cheque circulando, não perguntando “todo mundo conseguiu?”, que sempre recebe silêncio afirmativo.

Defenda a decisão antes de digitar a primeira linha. Se metade da turma estiver olhando em vez de digitando, pare e espere: quem não digitar esta infraestrutura chega sem projeto onde escrever o autômato.

Deslocamento em linha e coluna, por busca binária

Position SourceFile::positionAt(std::size_t offset) const {
    const std::size_t clamped = std::min(offset, text_.size());

    // upper_bound devolve o primeiro início de linha estritamente maior que o
    // deslocamento; a linha que contém o deslocamento é a anterior. Como
    // lineStarts_[0] é sempre 0 e clamped nunca é negativo, o iterador nunca
    // é begin(), e o decremento abaixo é seguro.
    const auto it = std::upper_bound(lineStarts_.begin(), lineStarts_.end(), clamped);
    const auto distance = std::distance(lineStarts_.begin(), it);
    const std::size_t index = static_cast<std::size_t>(distance) - 1;

    return Position{clamped, index + 1, clamped - lineStarts_[index] + 1};
}

Faça a conta com números concretos no quadro antes de projetar o código. Suponha linhas começando nos deslocamentos 0, 18, 45, 72 e 96; para o deslocamento 60 a busca encontra a maior entrada não superior a ele, que é 45, na terceira linha, e a coluna é 60 - 45 + 1 = 16.

Faça a conta ingênua ao lado, para mostrar o que se evita: varrer o texto contando quebras de linha custa tempo proporcional ao arquivo a cada erro reportado — cinquenta erros num arquivo de vinte mil caracteres percorrem um milhão de caracteres; com o índice de mil linhas, cada conversão custa cerca de dez comparações, e as cinquenta somam quinhentas.

Mencione, ao digitar a leitura do arquivo, a armadilha do ambiente: o fim de linha usa dois caracteres, e lido em modo texto o par pode virar um só, fazendo os deslocamentos deixarem de corresponder aos bytes. Ninguém descobre isso por raciocínio; todo mundo descobre depurando.

O diagnóstico é coletado, não lançado

// Coleta os diagnósticos de todas as fases em vez de abortar no primeiro.
// A partir do analisador léxico, cada fase reporta o que encontrar e segue;
// quem decide parar é o programa principal, olhando hasErrors().
class DiagnosticBag {
public:
    void report(Severity severity, Position position, std::string message);
    void error(Position position, std::string message);
    void warning(Position position, std::string message);

    bool hasErrors() const noexcept;
    std::size_t errorCount() const noexcept;
    std::size_t size() const noexcept;
    const std::vector<Diagnostic>& all() const noexcept;

    // Imprime no formato "arquivo:linha:coluna: severidade: mensagem",
    // seguido da linha ofensora e de um cursor sob a coluna.
    void printAll(const SourceFile& source, std::ostream& out) const;

A política é o conteúdo do slide, não a estrutura: cada fase registra o que encontra e segue, e quem decide parar é o programa principal.

Alguém vai objetar que isso é exagero num projeto sem fase alguma. Responda com o benefício datado, não com princípio geral: é o que torna possível, adiante, um analisador sintático que reporta três erros de uma vez.

Encerre a construção ligando o modo estrito antes de qualquer outra coisa, e diga em voz alta por quê: ligar as verificações rigorosas sobre uma base já escrita produz dezenas de erros de uma vez, e a reação natural diante desse muro é desligá-las de novo.

A mesma árvore, submetida aos dois tratamentos

// Emite em pós-ordem: primeiro os dois operandos, depois a operação. É a ordem
// natural para uma máquina de pilha, porque quando a instrução de operação
// executa os dois valores já estão empilhados.
void emitir(const No& no, std::vector<Instrucao>& saida) {
    if (no.tipo == TipoNo::Literal) {
        saida.push_back(Instrucao{OpCode::PushConst, no.valor});
        return;
    }

    emitir(*no.esquerda, saida);
    emitir(*no.direita, saida);

    switch (no.operador) {
        case Operador::Somar:
            saida.push_back(Instrucao{OpCode::Add, 0.0});
            break;
        case Operador::Subtrair:
            saida.push_back(Instrucao{OpCode::Sub, 0.0});
            break;
        case Operador::Multiplicar:
            saida.push_back(Instrucao{OpCode::Mul, 0.0});
            break;
    }
}

Desenhe no quadro a árvore de (2 + 3) \times 4 - 5 antes de projetar. O interpretador percorre a árvore em profundidade e devolve 15 agora; o compilador percorre a mesma árvore e devolve instruções que uma máquina de pilha executa depois.

Chame a atenção para a ordem da emissão: pós-ordem, primeiro os operandos e depois a operação. É a única que funciona numa máquina de pilha, porque no momento em que a operação executa os dois valores já precisam estar empilhados. É a mesma ordem que reaparece na geração de código, adiante.

Os dois caminhos, executando

$ peneira --demo
expressao: (2 + 3) * 4 - 5

caminho do interpretador
  percorre a arvore e produz o resultado agora
  resultado: 15

caminho do compilador
  percorre a arvore e produz instrucoes para depois
    0: PUSH_CONST 2
    1: PUSH_CONST 3
    2: ADD
    3: PUSH_CONST 4
    4: MUL
    5: PUSH_CONST 5
    6: SUB
  resultado da execucao: 15

Os dois caminhos chegam ao mesmo número, e essa igualdade é exatamente o contrato escrito na primeira aula. Retome a fórmula no quadro apontando para a tela.

Registre a armadilha, porque ela apanha quase todo mundo uma vez na vida: ao desempilhar, o segundo operando sai primeiro, por ter entrado por último. Trocar a ordem não altera nada na soma nem na multiplicação, e produz resultado errado na subtração. A expressão termina em subtração de propósito — é uma lição sobre escolher o caso de teste que revela o defeito.

A infraestrutura funciona antes de existir fase

$ peneira vazio.pen
vazio.pen:1:1: erro: arquivo de entrada vazio
codigo de saida: 1

A única verificação que este ponto do projeto consegue fazer é reclamar de arquivo vazio. Ela existe menos pela utilidade e mais como prova de que a infraestrutura de diagnóstico funciona ponta a ponta antes de haver qualquer fase que a use.

Chame a atenção para o formato — arquivo, linha, coluna, severidade, mensagem — e diga por que ele é esse: editores sabem interpretá-lo e transformam a saída em navegação clicável. E para o código de saída diferente de zero, que é o que permite ao build automatizado saber que houve erro.

O que a demonstração deixou provado

A evidência

Os dois caminhos chegam ao mesmo número. É a igualdade escrita na primeira aula, executando.

A confirmação do desenho

A infraestrutura de posição e diagnóstico funciona ponta a ponta antes de existir qualquer fase que a use.

O preço

Custo imediato, benefício remoto: nada aqui compila linguagem nenhuma, e é por isso que essa camada é sistematicamente adiada.

O terceiro cartão é o que separa demonstração de propaganda, e precisa ser dito em voz alta: quem escreve essa camada no primeiro dia paga sem receber nada visível em troca. O retorno aparece quando o analisador sintático reportar três erros de uma vez em vez de obrigar a corrigir e recompilar três vezes.

Deixe explícito à turma que a entrega deste módulo não compila linguagem nenhuma e que isso está certo — o que se entrega é decisão de projeto e infraestrutura. Grupos que tentam antecipar o analisador léxico terão de refazê-lo sobre uma teoria que ainda não conheciam.

Três defeitos, três destinos

Sem ponto e vírgula

Morre na análise sintática: a sequência de tokens não deriva da gramática.

Texto somado a número

Morre na análise semântica: a condição violada depende de contexto.

Laço que nunca termina

Não morre em lugar nenhum: pergunta sobre comportamento.

Bloco de fechamento. Volte ao quadro da abertura, às três palavras que ficaram no canto, e só então projete. A turma consegue preencher os três destinos sozinha — peça que preencham antes de você revelar.

Síntese

Um compilador é um tradutor sujeito a uma igualdade entre significados; distingue-se de um interpretador pelo quando, decompõe-se em fases definidas por interfaces, e vive nos dois andares inferiores de um mapa em que a forma das produções determina a memória necessária ao reconhecimento.

Contrato igualdade entre significados

Quando análise antes ou durante

Fases recebe, produz, detecta

Memória a escala da hierarquia

Retome os três objetivos do primeiro slide, um a um, citando o que da aula serve de evidência para cada um.

Anuncie o que a tutoria vai cobrar: formação dos grupos, escolha e defesa do domínio da linguagem, e o veredito sobre o tamanho do escopo ainda dentro deste módulo — depois fica caro trocar.

Se o tempo apertar, o que se corta é o slide dos dois avisos sobre o mapa; a escala de memória, não.

Referências

  • LOUDEN, Kenneth C. Compiladores: princípios e práticas. São Paulo: Cengage Learning, 2004.
  • AHO, Alfred V. Compiladores: princípios, técnicas e ferramentas. 2. ed. São Paulo: Pearson Education do Brasil, 2008.
  • MENEZES, Paulo B. Linguagens formais e autômatos. v. 3, 6. ed. Porto Alegre: Grupo A, 2011.
  • TURING, A. M. On Computable Numbers, with an Application to the Entscheidungsproblem. Proceedings of the London Mathematical Society, s2-42, p. 230-265, 1937.