Interpretador Python em 1024 Bytes: O Code Golf Mais Insano de 2026
Um interpretador Python completo. Com variáveis, loops, funções, recursão, if/else e até FizzBuzz. Tudo em 1024 bytes de C puro. Sem macros, sem bibliotecas externas, sem truque de compilador. Só um engenheiro da Microsoft, um fim de semana e uma obsessão doentia por bytes.
Austin Z. Henley publicou o projeto no blog dele e o código no GitHub, e a coisa explodiu no Hacker News. E eu entendo por quê: a combinação de C de baixo nível com Python de alto nível, tudo comprimido num espaço menor que este parágrafo, é o tipo de coisa que faz qualquer dev parar pra olhar.
Mas a graça não está só no resultado. Está no processo. Cada byte cortado exigiu uma decisão de engenharia real. E as técnicas que ele usou dizem muito sobre como compiladores e interpretadores funcionam por dentro.
O que esse interpretador roda?
Antes de falar do “como”, vale entender o “o quê”. O mini-interpretador suporta um subconjunto surpreendentemente funcional de Python:
| Feature | Suportado? | |
|---|---|---|
| ——— | ———– | |
| Variáveis inteiras (uma letra) | Sim | |
| Aritmética (+, -, *, %) | Sim | |
| Comparação (, =, ==) | Sim | |
| if/else | Sim | |
| while com else | Sim | |
| for-in-range | Sim | |
| Funções (sem argumentos) | Sim | |
| Recursão | Sim | |
| print (string e inteiro) | Sim | |
| Indentação como bloco | Sim | |
| Comentários (#) | Sim |
Ou seja, dá pra rodar isso aqui:
def fizzbuzz():
for n in range(101):
if n % 15 == 0:
print("FizzBuzz")
if n % 3 == 0:
print("Fizz")
if n % 5 == 0:
print("Buzz")
if n % 15 > 0:
if n % 3 > 0:
if n % 5 > 0:
print(n)
fizzbuzz()
FizzBuzz completo. Rodando num interpretador que cabe num tweet (dos antigos, de 1024 caracteres, se existisse).
A arquitetura por trás do micro-interpretador
Henley optou por um parser de descida recursiva que executa o código durante a análise. Nada de AST, nada de bytecode, nada de representação intermediária. O interpretador lê o código-fonte caractere por caractere e executa cada instrução na hora.
O estado inteiro do programa vive em variáveis globais:
char src[999]; // código-fonte (sem espaços)
int vars[256]; // tabela de símbolos
int pos; // posição atual no source
int ch; // caractere atual
int line_start; // início da linha
Cinco variáveis. Isso é tudo que separa texto plano de execução de código.
A tabela de símbolos é engenhosa: um array de 256 posições indexado pelo valor ASCII do caractere. A variável x vive em vars[120] (porque 'x' é 120 em ASCII). Funções também: quando você define def f():, o interpretador salva a posição no código-fonte em vars['f'], e quando chama f(), ele pula pra aquela posição, executa, e volta.
Sem alocação dinâmica. Sem hash table. Só um array e aritmética de ponteiro.
Como blocos de código funcionam sem tokens
Python usa indentação para delimitar blocos. Implementar isso num parser normal já é chatento. Fazer em 1024 bytes é sadomasoquismo.
A solução foi a função run_block(), que continua executando linhas enquanto a indentação for maior ou igual à do bloco atual. Quando a indentação diminui, o bloco acabou. A pilha de chamadas do C cuida do escopo. Elegante, na verdade: o sistema de escopo do C funciona como proxy para o sistema de escopo do Python. Cada chamada recursiva de run_block() cria um frame na stack que “lembra” qual era o nível de indentação esperado.
Loops (while e for) funcionam de um jeito brutal: em cada iteração, o interpretador volta pra posição no código-fonte onde o loop começa e re-parseia tudo. Sim, ele relê e reinterpreta o código a cada volta do loop. Ineficiente? Absurdamente. Um loop de mil iterações significa parsear o mesmo trecho de texto mil vezes. Mas funcional, e economiza bytes que seriam gastos gerenciando estado de iteração.
O for-in-range é particularmente interessante. O interpretador precisa parsear for x in range(N):, extrair o nome da variável, o limite do range, e gerenciar a iteração. Tudo isso em poucos bytes. A implementação guarda a posição do início do bloco, incrementa a variável a cada volta, e compara com o limite. Quando o contador atinge N, o interpretador avança para depois do bloco e continua a execução normal.
E o else nos loops? Sim, o Python tem isso (muita gente nem sabe). while x > 0: ... else: ... executa o bloco else quando a condição do while fica falsa. O interpretador do Henley suporta isso. Em 1024 bytes. Às vezes você precisa parar e apreciar a insanidade.
Code golf: as técnicas que cortaram 4x o tamanho
A versão legível do interpretador tem mais de 4.800 bytes. A versão golfada tem exatamente 1.024. Como cortar quase 80% do código?
Nomes de uma letra
Toda variável, toda função, todo parâmetro vira uma letra. parse_sum() vira e(). get_char() vira G(). run_block() vira B(). Economiza 5 a 15 bytes por identificador, e num programa com dezenas de funções, isso se acumula rápido.
Aritmética ASCII em vez de caracteres
Em C, '+' é 43. Então em vez de comparar c == '+', você compara c == 43 (ou melhor, c-43 e checa se é zero). Mas vai além: o interpretador usa relações aritméticas entre valores ASCII pra agrupar comparações.
A expressão c-43u<3 checa se o caractere é +, , ou - de uma vez, porque seus valores ASCII (43, 44, 45) são consecutivos. Uma comparação que substituiu três.
Funções recursivas de uma linha
A função que pula até o fim da linha:
Y(){c&&c-10&&Y(G());}
Traduzindo: “se o caractere atual não é nulo E não é newline, leia o próximo caractere e chame a si mesma”. Short-circuit evaluation do && elimina a necessidade de if/else.
Explorar C89 ao máximo
C89 permite declaração implícita de tipo (tudo é int por padrão), permite funções sem return explícito, e permite argumentos sem tipo. Cada int removido economiza 4 bytes. Cada return economiza 7. Num programa de 1024 bytes, 4 bytes são 0,4% do total.
Operador vírgula e ternário no lugar de blocos
Em vez de:
if (condition) {
a = 1;
b = 2;
}
Você escreve:
condition ? (a=1,b=2) : 0;
Uma linha. Sem chaves, sem quebra de linha, sem espaço desperdiçado.
O que foi cortado pra caber
Code golf é tanto sobre o que você tira quanto sobre como comprime o que fica. Henley documentou os sacrifícios:
Sem tratamento de erro. Se o código Python tiver syntax error, o interpretador simplesmente faz algo imprevisível. Pode travar, pode rodar lixo, pode entrar em loop infinito. Validação de entrada custa bytes.
Variáveis de uma letra só. Você não pode usar total ou counter. Só a até z. O array de 256 posições permite usar qualquer caractere ASCII como variável, mas na prática só letras minúsculas fazem sentido.
Funções sem argumentos. def f(): funciona, def f(x): não. Pra passar dados entre funções, você usa variáveis globais. É feio? Sim. Mas economiza todo o parsing de parâmetros.
Uma comparação por expressão. if x > 5: funciona. if x > 5 and x < 10: não. O parser de expressões não suporta operadores lógicos compostos.
Sem escopo léxico. Todas as variáveis são globais. Uma função pode ler e escrever qualquer variável de qualquer lugar. Isso é tecnicamente mais parecido com BASIC dos anos 70 do que com Python moderno, mas pro FizzBuzz funciona perfeitamente.
Henley estima que se o objetivo fosse apenas rodar FizzBuzz (sem suportar features genéricas), o interpretador poderia caber em menos de 800 bytes.
O parse de expressões: precedência em miniatura
Se você já estudou compiladores, sabe que precedência de operadores (multiplicação antes de soma) é um dos trechos mais verbosos de qualquer parser. O Henley resolveu isso com duas funções mutuamente recursivas: uma para soma/subtração, outra para multiplicação/módulo. A função de soma chama a de multiplicação internamente, garantindo que 2 + 3 * 4 resulta em 14, não 20.
Na versão golfada, a função de soma virou isso:
e(){for(z=t();c-43u<3;)y=44-c,z+=y*t();return z;}
t() é a função de multiplicação. c-43u<3 checa se o operador é +, , ou - (ASCII 43, 44, 45). O y=44-c transforma + em 1 e - em -1, permitindo que a mesma operação z+=y*t() sirva para soma e subtração. É o tipo de hack que faz você fechar o laptop e ir tomar um café.
Expressões atômicas (números e variáveis) são tratadas na base da recursão. Um número é parseado caractere por caractere, acumulando dígitos: n = n * 10 + (c - 48), onde 48 é o ASCII de '0'. Uma variável é simplesmente vars[c], onde c é o caractere lido.
Por que isso importa (de verdade)
“Legal, mas pra quê?” é a pergunta óbvia. E a resposta tem camadas.
Primeiro, entender compiladores de verdade. O interpretador do Henley usa as mesmas técnicas fundamentais que o CPython real: parsing recursivo, tabela de símbolos, avaliação de expressões com precedência de operadores. A diferença é que o CPython tem 600.000+ linhas de código e gera bytecode intermediário. Mas o princípio é o mesmo.
Segundo, limitações forçam criatividade. A restrição de 1024 bytes obrigou decisões de design que normalmente são evitadas: execução durante parsing, re-parsing de loops, variáveis globais como mecanismo de comunicação. Cada uma dessas decisões é “errada” em software profissional, mas funcional dentro da restrição. E entender por que essas decisões são ruins no contexto normal ajuda a entender por que as decisões “certas” existem.
Terceiro, C continua sendo a linguagem do metal. Em 2026, com LLMs escrevendo código, frameworks abstraindo tudo e devs que nunca viram um ponteiro, um projeto como esse é um lembrete de que toda abstração é, no fundo, bytes numa memória. O array vars[256] indexado por ASCII é primitivo, mas é exatamente o mesmo conceito por trás de uma hash table, só sem o hashing.
Como compilar e testar
Se você quer rodar isso na sua máquina, é trivial:
git clone https://github.com/AZHenley/python1024.git
cd python1024
gcc -o python1024 python1024_golf.c
echo 'print("hello world")' | ./python1024
O binário compilado é minúsculo (uns poucos KB), compila sem warnings em C89, e roda qualquer programa Python que respeite as limitações listadas acima.
Pra quem quer ir além, o repositório inclui a versão legível (python1024_readable.c) com comentários explicando cada função. É um exercício excelente de engenharia reversa: compare as duas versões lado a lado e tente entender como cada transformação de golf funciona.
Comparação com outros interpretadores minimalistas
| Projeto | Linguagem | Tamanho | Interpreta | |
|---|---|---|---|---|
| ——— | ———– | ——— | ———— | |
| python1024 | C | 1024 bytes | Subconjunto de Python | |
| tcc | C | ~100KB | C completo | |
| MicroPython | C | ~256KB | Python 3 (embarcados) | |
| picoc | C | ~15KB | Subconjunto de C | |
| Lua | C | ~250KB | Lua completa |
O python1024 é 250x menor que o MicroPython e, proporcionalmente, suporta uma quantidade impressionante de features. A comparação não é justa (MicroPython é um interpretador de produção), mas ilustra o ponto: com as restrições certas, você consegue fazer muito com pouco.
O que o Hacker News pensou
O post acumulou mais de 250 upvotes e centenas de comentários. Os destaques:
Muita gente tentou estender o interpretador, adicionando suporte a strings, arrays ou argumentos de função, e relatou como cada feature nova estourava o limite de bytes. Outros sugeriram técnicas de golf ainda mais agressivas, como usar defines de pré-processador (que Henley deliberadamente evitou por considerar “trapaça”).
Um comentário que resume bem o sentimento geral: “Projetos assim me lembram por que eu comecei a programar. Não pra entregar ticket, mas pra resolver puzzles impossíveis.”
Outros apontaram que o projeto é um excelente exercício didático. Entender como um interpretador funciona é uma daquelas skills que transforma um dev mediano num dev que realmente entende o que está acontecendo debaixo do capô. E ler 1024 bytes de C golfado é infinitamente mais acessível do que mergulhar nos 600.000+ linhas do CPython.
Desafio pro leitor
Se você leu até aqui e está com as mãos coçando, aqui vai um desafio: tente adicionar suporte a argumentos de função (def f(x):) sem ultrapassar 1100 bytes. Vai precisar de parsing de parâmetros na definição, push/pop de argumentos na chamada, e algum mecanismo de escopo (mesmo que fake). O repositório no GitHub aceita PRs.
Ou, se quiser algo menos ambicioso: tente escrever o menor interpretador de Brainfuck em C. São só 8 instruções. Se o Henley fez Python em 1024 bytes, Brainfuck em 256 deveria ser factível.
O código está no GitHub do Austin Henley: github.com/AZHenley/python1024. O post original com toda a explicação técnica está no blog dele.
Fonte de inspiração: Making a Python interpreter in 1024 bytes, por Austin Z. Henley













