Interpretadores
A forma mais direta de executar código: percorrer a AST nodo a nodo, avaliando expressões e modificando um ambiente de variáveis.
Executar a árvore diretamente
Um interpretador tree-walking pega a AST e a percorre recursivamente:
ao encontrar um BinOp(+), avalia os dois filhos e retorna a soma.
Ao encontrar um If, avalia a condição e executa o branch correto.
Um ambiente (environment) guarda as variáveis — é basicamente um dicionário
{nome: valor}.
Interpretadores são simples de implementar (centenas de linhas de código), portáteis e fáceis de depurar. A desvantagem: são mais lentos que compiladores, pois percorrem a AST toda vez que o código executa — mesmo em loops que rodam milhões de vezes.
Interprete programas passo a passo
Escolha um programa e clique em Próximo ou Executar. Acompanhe a linha atual, o ambiente de variáveis e a saída.
Tree-walking interpreter
// Tree-walking interpreter — executa a AST diretamente function evaluate(node, env) { switch (node.type) { case "Num": return node.value; case "Id": if (!(node.name in env)) throw new Error(`Undefined: ${node.name}`); return env[node.name]; case "BinOp": { const l = evaluate(node.left, env); const r = evaluate(node.right, env); if (node.op === "+") return l + r; if (node.op === "-") return l - r; if (node.op === "*") return l * r; if (node.op === "/") return l / r; } case "Let": env[node.name] = evaluate(node.value, env); return undefined; case "If": if (evaluate(node.cond, env)) executeBlock(node.then, env); else if (node.else) executeBlock(node.else, env); return undefined; case "While": while (evaluate(node.cond, env)) executeBlock(node.body, env); return undefined; case "Print": console.log(evaluate(node.expr, env)); return undefined; } }
Escopos léxicos
// Escopos léxicos — cada bloco tem seu ambiente function makeEnv(parent = null) { const bindings = {}; return { get(name) { if (name in bindings) return bindings[name]; if (parent) return parent.get(name); // busca no pai throw new Error(`Undefined: ${name}`); }, set(name, value) { bindings[name] = value; }, }; } // Ao entrar num bloco (if, while, função): const outer = makeEnv(); outer.set("x", 10); const inner = makeEnv(outer); // inner vê x do outer inner.set("y", 5); inner.get("x"); // → 10 (sobe pela cadeia de escopos)
Implemente um interpretador
Mini projeto: implemente evaluate(ast, env) para suportar: números, variáveis, as 4 operações aritméticas, comparações (==, !=, <, >), e print. Teste com 5 expressões diferentes.
Projeto principal: adicione suporte a let, if/else, while, e funções com parâmetros e retorno. Implemente escopos léxicos corretamente (funções devem capturar o escopo onde foram definidas). Teste com recursão (fatorial).
Desafio extra: implemente um debugger para seu interpretador: adicione breakpoints (pontos de parada), inspeção de variáveis, e execução passo a passo com step-in/step-over. É assim que debuggers de linguagens interpretadas funcionam.
Teste sua intuição
Onde você encontra isso
CPython
O interpretador oficial de Python compila para bytecode (.pyc) e depois interpreta o bytecode numa VM chamada CPython. O tree-walking puro foi substituído por bytecode na versão 1.5 (1997) para ganhar velocidade.
Bash e shell scripts
Shells Unix (bash, zsh, fish) são interpretadores puros: leem o script linha a linha, parseiam e executam imediatamente. Não há passo de compilação — por isso scripts de 1000 linhas são perceptivelmente mais lentos.
Lua em jogos
Lua é a linguagem de script embutida em jogos como World of Warcraft e Roblox. É compilada para bytecode e interpretada por uma VM compacta (~150kb) que pode ser embutida em qualquer aplicação C.
Cel (Google)
Common Expression Language — linguagem interpretada usada em Kubernetes, Firebase e Cloud IAM para avaliar expressões de política de segurança em tempo real sem riscos de execução arbitrária.