DEV Community

pyrtugues
pyrtugues

Posted on

Como funciona o tradutor do Pyrtugues

Como funciona o tradutor do Pyrtugues por dentro

Tempo de leitura: ~6 minutos

No meu primeiro post eu apresentei o Pyrtugues: uma linguagem de programação em português que traduz código para Python real. Muita gente perguntou como isso funciona por dentro.

Esse post responde. Vou mostrar os desafios técnicos de traduzir código entre linguagens e como resolvi cada um.

Se você nunca escreveu um tradutor, esse post é pra você. Se já escreveu, talvez encontre algo interessante.


O problema básico

Traduzir mostrar("Olá") para print("Olá") parece simples. E é — se você só tem uma linha.

O problema começa quando o código tem strings, comentários e f-strings. Veja esse exemplo:

nome = "Maria e João"
mostrar(f"Olá, {nome}!")
# Isso é um comentário com se, senão, mostrar
Enter fullscreen mode Exit fullscreen mode

Se você fizer uma tradução ingênua (substituir toda palavra-chave), o resultado vira uma bagunça:

nome = "Maria and João"     # ❌ ERRADO - "e" virou "and"
print(f"Olá, {nome}!")       # ✅ Ok
# Isso é um comentário com if, else, print   # ❌ ERRADO
Enter fullscreen mode Exit fullscreen mode

Três problemas apareceram:

  1. O "e" dentro da string virou "and"
  2. O comentário foi traduzido
  3. O Python não entende isso

A solução: proteger strings antes de traduzir

A primeira coisa que o tradutor faz é esconder todas as strings e comentários antes de mexer no código. Uso um scanner que percorre caractere por caractere:

def proteger_strings_e_comentarios(codigo):
    blocos = []
    resultado = []
    i = 0

    while i < len(codigo):
        # Comentário: protege tudo depois de #
        if codigo[i] == "#":
            inicio = i
            while i < len(codigo) and codigo[i] != "\n":
                i += 1
            resultado.append(guardar(codigo[inicio:i]))
            continue

        # String: protege tudo entre aspas
        if codigo[i] in '"\'':
            # ... lógica de detecção ...
            resultado.append(guardar(codigo[inicio:i]))
            continue

        resultado.append(codigo[i])
        i += 1

    return "".join(resultado), blocos
Enter fullscreen mode Exit fullscreen mode

Cada string/comentário vira um placeholder único tipo __PYRT_STR_0__. Depois o tradutor roda em paz, e no final os placeholders são restaurados.

Resultado: as strings ficam intocadas. ✅


O caso mais difícil: f-strings

F-strings são strings especiais que contêm expressões Python dentro de chaves:

mostrar(f"Olá, {nome}! Você tem {idade mais 1} anos.")
Enter fullscreen mode Exit fullscreen mode

O tradutor precisa:

  1. Não traduzir o texto normal da f-string ("Olá, ...")
  2. Traduzir as expressões dentro das chaves ({idade mais 1} → {idade + 1})

A solução é processar cada f-string separadamente, identificando onde começa e termina cada {...}:

def _traduzir_fstring(match):
    texto = match.group(0)
    # ... encontrar prefixo e aspas ...
    corpo = texto[...]

    partes = []
    i = 0
    while i < len(corpo):
        if corpo[i] == '{':
            # Encontrar o } correspondente
            profundidade = 1
            j = i + 1
            while j < len(corpo) and profundidade:
                if corpo[j] == '{': profundidade += 1
                elif corpo[j] == '}': profundidade -= 1
                j += 1

            # Traduzir a expressão interna
            expr = corpo[i+1:j-1]
            partes.append('{' + traduzir_expressao(expr) + '}')
            i = j
        else:
            partes.append(corpo[i])
            i += 1

    return prefixo + quote + ''.join(partes) + quote
Enter fullscreen mode Exit fullscreen mode

Isso funciona mesmo com f-strings aninhadas ou expressões complexas como {minha_lista[0] mais 10}.


O truque das palavras contextuais

Palavras como se, em, e, ou são ambíguas. Podem ser:

  • Palavra-chave (se idade > 10:)
  • Nome de variável (se = 10)
  • Parte de outra palavra (sete, sempre, tema)

Uso regex com contexto específico pra cada caso:

se só traduz no início de linha

# Só vira "if" se for início de uma condição
resultado = re.sub(
    r'^(\s*)se(?=\s+.+:)',  # início + indentação + se + expressão + :
    r'\1if',
    resultado,
    flags=re.MULTILINE
)
Enter fullscreen mode Exit fullscreen mode

Isso garante que se idade > 10: vira if idade > 10:, mas se = 10 continua sendo variável.

em só traduz em loops

# Só vira "in" em "for x em y"
resultado = re.sub(
    r'\bfor\s+(\w+)\s+em\b',
    r'for \1 in',
    resultado
)
Enter fullscreen mode Exit fullscreen mode

e / ou só entre expressões

# Só vira "and" / "or" entre duas expressões
resultado = re.sub(
    r'([a-zA-Z0-9_\)\]]+)\s+e\s+([a-zA-Z0-9_\(\[\{])',
    r'\1 and \2',
    resultado
)
Enter fullscreen mode Exit fullscreen mode

Isso evita que "bom dia e boa tarde" vire "bom dia and boa tarde".


Métodos: só depois do ponto

Em Python, métodos são chamados depois de um ponto:

texto.upper()
lista.append("item")
Enter fullscreen mode Exit fullscreen mode

Em Pyrtugues, usamos palavras em português nos mesmos lugares:

texto.maiúsculas()
lista.adicionar("item")
Enter fullscreen mode Exit fullscreen mode

A tradução usa lookbehind pra garantir que só traduz depois do ponto:

# (?<=\.) significa "precedido por ."
resultado = re.sub(
    rf'(?<=\.)\s*{palavra}(?=\s*\()',
    traducao,
    resultado
)
Enter fullscreen mode Exit fullscreen mode

Isso evita que uma variável chamada maiúsculas seja traduzida por engano.


O resultado final

O tradutor completo faz isso em sequência:

1. Proteger strings e comentários →  __PYRT_STR_0__, __PYRT_STR_1__...
2. Traduzir expressões dentro de f-strings
3. Traduzir palavras-chave com contexto (se, em, e, ou)
4. Traduzir métodos (só depois do ponto)
5. Traduzir operadores (mais, menos, vezes)
6. Restaurar strings e comentários
7. Retornar código Python
Enter fullscreen mode Exit fullscreen mode

Linha 1 do Pyrtugues:

se idade maior que 10:
    mostrar("Olá!")
Enter fullscreen mode Exit fullscreen mode

Linha 1 do Python gerado:

if idade > 10:
    print("Olá!")
Enter fullscreen mode Exit fullscreen mode

E funciona com código complexo:

função calcular_media(notas):
    soma = 0
    para nota em notas:
        soma = soma mais nota
    retornar soma dividido por tamanho(notas)

nomes = ["Ana e Maria", "João e Pedro"]
para nome em nomes:
    mostrar(f"Processando: {nome}")
Enter fullscreen mode Exit fullscreen mode

Vira:

def calcular_media(notas):
    soma = 0
    for nota in notas:
        soma = soma + nota
    return soma / len(notas)

nomes = ["Ana e Maria", "João e Pedro"]
for nome in nomes:
    print(f"Processando: {nome}")
Enter fullscreen mode Exit fullscreen mode

Os "e" dentro das strings ficam preservados. ✅


O que aprendi nesse projeto

Fazer um tradutor parece simples até você tentar. Aprendi três lições:

  1. Contexto é tudo. Substituir palavra por palavra não funciona. Precisa entender onde cada uma está.
  2. Strings são sagradas. Proteja antes de qualquer coisa.
  3. Teste com código real. Bug só aparece quando você tenta casos extremos.

O código completo tá no GitHub. Se você quiser contribuir com melhorias no tradutor, abre uma issue ou manda PR.


Vem testar

O Pyrtugues tá disponível em:

Ficou alguma dúvida sobre o tradutor? Quer saber mais sobre alguma parte específica? Comenta aqui embaixo que eu respondo!


📚 Posts anteriores


Pyrtugues — Programação em português. Lógica de Python. Uma ponte para aprender.

Criado por Vinicius Caracciolo (12 anos)

Top comments (0)