Design de software

Padrão Interpreter em Swift: crie um interpretador de minilinguagem

Ao abrir um livro de padrões de projeto, o padrão Interpreter quase sempre aparece perto do fim. É fácil passar por ele pensando: “Quando isso é usado?”

5 min de leitura
Imagem de capa de Padrão Interpreter em Swift: crie um interpretador de minilinguagem

Ao abrir um livro de padrões de projeto, o padrão Interpreter quase sempre aparece perto do fim. É fácil passar por ele pensando: “Quando isso é usado?”

Porém, ao criar um app de calculadora, chegou o momento de calcular diretamente expressões matemáticas em strings. Foi quando o padrão Interpreter se encaixou perfeitamente.

Neste artigo, vamos criar em Swift um interpretador de linguagem muito pequeno, ou seja, uma minilinguagem. O objetivo é ler expressões como 1 + 2 * 3 e retornar o número 7.

Em resumo, a conclusão é esta:

O padrão Interpreter transforma cada regra gramatical em uma classe (ou enum), monta tudo em uma árvore e percorre essa estrutura recursivamente para obter o resultado.

Parece difícil, mas o código é mais simples do que parece. Vamos construir juntos.

O que é o padrão Interpreter?

O padrão Interpreter é uma forma de criar uma “linguagem pequena própria” e representar em código as regras para interpretá-la.

Não estamos falando de uma linguagem grandiosa. Qualquer expressão pequena com uma gramática definida, como uma fórmula, um filtro de busca ou um script de regras de jogo, se enquadra aqui.

A ideia central é uma só.

Transforme cada elemento da gramática em um objeto e faça esses objetos terem um método interpret() que interprete a si próprio.

Por exemplo, pense na expressão 1 + 2. Ela contém o número 1, o número 2 e a operação de adição +.

O número é interpretado como “vou retornar meu valor”, enquanto a adição é interpretada como “vou interpretar a esquerda e a direita e somá-las”.

Essas pequenas regras formam uma árvore; basta chamar a interpretação uma vez no topo para concluir o cálculo descendo pela estrutura.


Projetando uma árvore de expressões em Swift

No Swift, enum combina muito bem com esse padrão. Com um enum recursivo, é possível representar a árvore de forma bem limpa.

No início, implementei com protocolos e classes, mas trocar para enum reduziu o código pela metade.

Primeiro, definimos um enum que representa uma expressão: um número único e os casos de somar ou multiplicar duas expressões.

Como esse enum contém a si próprio, precisamos adicionar a palavra-chave indirect.

indirect enum Expr {
    case number(Double)        // literal numérico
    case add(Expr, Expr)       // adição
    case multiply(Expr, Expr)  // multiplicação
}

Agora criamos uma função para interpretar essa árvore. Processar cada caso recursivamente é o núcleo do padrão Interpreter.

A função a seguir recebe uma expressão, calcula seu valor numérico real e o retorna.

func interpret(_ expr: Expr) -> Double {
    switch expr {
    case .number(let value):
        return value
    case .add(let l, let r):
        return interpret(l) + interpret(r)
    case .multiply(let l, let r):
        return interpret(l) * interpret(r)
    }
}

A partir daqui, podemos montar 1 + 2 * 3 como uma árvore e calculá-la.

Se criarmos a árvore manualmente, como em .add(.number(1), .multiply(.number(2), .number(3))), e a passarmos para interpret, o resultado será 7.

A multiplicação precisa ser agrupada primeiro para manter a ordem correta
A multiplicação precisa ser agrupada primeiro para manter a ordem correta

Como transformar uma expressão em string em uma árvore?

Aqui surge uma dúvida: o usuário não digita uma árvore, mas uma string como "1 + 2 * 3".

O processo de transformar essa string em uma árvore se chama parsing, e o código que faz isso é o parser.

Na abordagem tradicional, o parser é dividido em duas etapas.

  1. Lexer: divide a string em tokens (por exemplo: 1, +, 2)
  2. Parser: monta os tokens em uma árvore de acordo com as regras gramaticais

Implementar bem essas duas etapas deixaria o artigo longo demais, então aqui vamos tratar apenas do conceito.

O ponto principal é este: o padrão Interpreter cuida apenas da interpretação da árvore. Fazer o parsing da string para convertê-la em árvore é outra tarefa.

Por isso, durante o aprendizado, recomendo deixar o parser de lado por um momento, montar a árvore manualmente e concluir primeiro o interpretador. Assim, a essência do padrão fica clara.

Código na tela, árvore no caderno: desenhar assim torna tudo muito mais fácil de entender
Código na tela, árvore no caderno: desenhar assim torna tudo muito mais fácil de entender

Onde isso é usado na prática?

Sinceramente, não é comum implementar o padrão Interpreter do zero. Para linguagens complexas, é muito melhor usar uma biblioteca de parsing já consolidada.

Ainda assim, entender esse padrão traz muitos benefícios.

  • Ajuda a entender como um mecanismo de expressões regulares funciona internamente
  • Permite entender por que a sintaxe declarativa do SwiftUI tem uma estrutura de árvore
  • Pode ser aplicado diretamente na criação de uma DSL (Domain-Specific Language, linguagem específica de domínio) pequena, como um filtro de busca ou um avaliador de expressões

Especialmente quando a gramática é simples e o escopo é restrito, como em uma calculadora, um filtro condicional ou um script de jogo, fazer você mesmo pode ser mais limpo.

Depois que aprendi esse padrão, a estrutura de views do SwiftUI passou a parecer completamente diferente. Cada view era, no fim, um nó da árvore interpretada.

Depois de conhecer esse padrão, as views do SwiftUI parecem diferentes
Depois de conhecer esse padrão, as views do SwiftUI parecem diferentes

Resumo

Hoje vimos o padrão Interpreter enquanto criávamos um interpretador de minilinguagem em Swift.

Criar uma árvore de expressões com um enum recursivo e interpretá-la recursivamente com a função interpret. É só isso.

Mesmo que pareça estranho no início, ao calcular 1 + 2 * 3 por conta própria chega o momento de pensar: “Ah, então era isso”. Espero que você digite pelo menos uma vez o código aprendido hoje.

Continue lendo